Timeline Component¶
Navigation: Home → Features & Components → Timeline
Table of Contents¶
- Introduction
- Component Structure
- Smart Date Formatting
- Visual Design
- TimelineItem Props
- Usage Examples
- See Also
- Next Steps
Introduction¶
The Timeline component (components/timeline.tsx) displays professional experience, education, and career milestones in a vertical timeline format. It features company logos, project links, and intelligent date formatting that automatically converts future end dates to "Present" for ongoing positions.
Key Features¶
- ✅ Smart Date Formatting: Converts future dates to "Present" automatically
- ✅ Company Logos: Display circular company/organization logos
- ✅ Project Links: Optional links to related projects
- ✅ External Company Links: Link company names to websites
- ✅ Responsive Design: Adapts to mobile and desktop
- ✅ Clean Visual Design: Vertical line with markers
- ✅ Client Component: Interactive date updates
Component Structure¶
File Overview¶
```typescript:1:152:components/timeline.tsx "use client";
import { cn } from "@/lib/utils"; import Image from "next/image"; import Link from "next/link"; import { ExternalLink } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useEffect, useState } from "react";
interface TimelineItemProps { year: string; title: string; company: string; description: string; logo?: string; projectLink?: string; companyUrl?: string; /* * If true, formats date ranges to replace future end dates with "Present". / smartDates?: boolean; }
### Component Architecture
```mermaid
graph TB
Timeline[Timeline Container] --> Items[TimelineItem x N]
Items --> Marker{Logo?}
Marker -->|Yes| LogoMarker[Company Logo]
Marker -->|No| DotMarker[Simple Dot]
Items --> Content[Timeline Content]
Content --> DateDisplay[DateDisplay Component]
Content --> TitleRow[Title + Project Link]
Content --> CompanyInfo[Company Name + Link]
Content --> Description[Description Text]
DateDisplay --> SmartCheck{smartDates enabled?}
SmartCheck -->|Yes| FormatDate[formatDateRange]
SmartCheck -->|No| RawDate[Display as-is]
FormatDate --> FutureCheck{End date in future?}
FutureCheck -->|Yes| ShowPresent["Display 'Present'"]
FutureCheck -->|No| ShowDate[Display end date]
style Timeline fill:#e1f5ff
style Content fill:#d4edda
style DateDisplay fill:#fff3cd
Smart Date Formatting¶
The Problem¶
Timeline data may include future end dates for ongoing positions:
Issue: When June 2026 arrives, the entry still shows "Jun. 2026" instead of "Present".
The Solution¶
```typescript:24:47:components/timeline.tsx function formatDateRange(year: string): string { // Handle date ranges like "Jan. 2026 - Jun. 2026" if (year.includes(" - ")) { const [start, end] = year.split(" - ").map((s) => s.trim());
// Parse end date to check if it's in the future
// Create a proper date from "Mon. YYYY" format for cross-browser compatibility
const monthYearRegex = /([A-Za-z]+)\.\s(\d{4})/;
const match = end.match(monthYearRegex);
if (match) {
const [, monthStr, yearStr] = match;
// Create date with the first day of the month to ensure it's valid
const endDate = new Date(`${monthStr} 1, ${yearStr}`);
const today = new Date();
// If end date is in the future, replace with "Present"
if (endDate > today) {
return `${start} - Present`;
}
}
} return year; } ```
Algorithm Breakdown¶
- Check for Date Range
typescript if (year.includes(" - ")) - Split Start and End
typescript const [start, end] = year.split(" - ").map((s) => s.trim()); // "Jan. 2024 - Jun. 2026" → ["Jan. 2024", "Jun. 2026"] - Parse End Date
typescript const monthYearRegex = /([A-Za-z]+)\.\s(\d{4})/; // Matches: "Jun. 2026" → ["Jun", "2026"] - Create Valid Date
typescript const endDate = new Date(`${monthStr} 1, ${yearStr}`); // "Jun. 2026" → Date("Jun 1, 2026")Why first of month? Ensures valid date across all months (avoids Feb 30, etc.) - Compare with Today
typescript if (endDate > today) { return `${start} - Present`; }
DateDisplay Component¶
typescript:49:59:components/timeline.tsx
function DateDisplay({ year, smartDates = false }: { year: string; smartDates?: boolean }) {
const [displayYear, setDisplayYear] = useState(year);
useEffect(() => {
if (smartDates) {
setDisplayYear(formatDateRange(year));
}
}, [year, smartDates]);
return <div className="text-muted-foreground mb-1 pt-5 text-sm">{displayYear}</div>;
}
Client-side Processing:
- Uses
useEffectto format on mount - Responds to prop changes
- Maintains original date in data source
Visual Design¶
Timeline Layout¶
Text Content Visual Markers
════════════ ══════════════
Mar. 2024 - Present
Senior Developer ●───[LOGO]
Company Name │
Description text... │
│
Jan. 2023 - Feb. 2024 │
Junior Developer ●───[LOGO]
Another Company │
Description text... │
│
Jul. 2021 - Dec. 2022 │
Intern ●
First Company │
Description text... ─
CSS Structure¶
```typescript:72:94:components/timeline.tsx
{/* Company logo or circle marker */}
{logo ? (
<div
className={cn(
"bg-card absolute top-12 -left-6 h-12 w-12 rounded-full border-[1.5px] p-0.5"
)}
>
<Image
src={logo}
alt={`${company} logo`}
width={48}
height={48}
className="h-full w-full rounded-full object-contain text-xs"
/>
</div>
) : (
<div className="bg-primary absolute top-12 left-[-3.5px] mt-5 h-2 w-2 rounded-full"></div>
)}
```
Logo Marker¶
- Size: 48x48px (
h-12 w-12) - Position: Absolute, centered on vertical line (
-left-6) - Styling: Rounded, bordered, white background
- Fallback: Small dot if no logo provided
Vertical Line¶
typescript
className = "bg-border absolute top-0 bottom-0 left-0 w-px";
- Full height of timeline item
- 1px width
- Uses theme border color
- Absolute positioning
TimelineItem Props¶
Interface Definition¶
interface TimelineItemProps {
year: string; // Date or date range (e.g., "Jan. 2024 - Jun. 2026")
title: string; // Position title
company: string; // Company/organization name
description: string; // Role description
logo?: string; // Optional company logo path
projectLink?: string; // Optional link to related project
companyUrl?: string; // Optional company website URL
smartDates?: boolean; // Enable smart date formatting (default: false)
}
Example Data¶
const timelineData: TimelineItemProps[] = [
{
year: "Jan. 2024 - Jun. 2026",
title: "Senior Software Engineer",
company: "Tech Corp",
companyUrl: "https://techcorp.com",
description: "Lead development of cloud-based solutions...",
logo: "/images/companies/techcorp.png",
projectLink: "/projects/cloud-platform",
smartDates: true,
},
{
year: "Mar. 2022 - Dec. 2023",
title: "Full Stack Developer",
company: "Startup Inc",
description: "Built scalable web applications...",
// No logo, no links
},
];
Project Link¶
```typescript:100:110:components/timeline.tsx {projectLink && ( )}
**Smart External Link Detection:**
- If starts with `http`: Opens in new tab
- Internal link: Normal navigation
### Company Link
```typescript:113:127:components/timeline.tsx
<p className="text-muted-foreground mb-2">
{companyUrl ? (
<Link
href={companyUrl}
target="_blank"
rel="noopener noreferrer"
className="underline-offset-4 hover:underline"
>
{company}
<ExternalLink className="ml-1 inline size-3 align-baseline" />
</Link>
) : (
company
)}
</p>
Usage Examples¶
Basic Timeline¶
import { Timeline } from "@/components/timeline";
const experienceData = [
{
year: "2023 - Present",
title: "Software Engineer",
company: "Company A",
description: "Working on exciting projects..."
},
{
year: "2021 - 2023",
title: "Junior Developer",
company: "Company B",
description: "Learned the fundamentals..."
}
];
export default function ExperiencePage() {
return (
<section>
<h2>Work Experience</h2>
<Timeline items={experienceData} />
</section>
);
}
With Smart Dates¶
const currentPositions = [
{
year: "Jan. 2024 - Dec. 2025", // Future end date
title: "Senior Engineer",
company: "Current Co",
description: "Leading the team...",
smartDates: true // Will show "Jan. 2024 - Present"
}
];
<Timeline items={currentPositions} smartDates />
Full Featured¶
const fullTimeline = [
{
year: "Jan. 2024 - Present",
title: "Senior Software Engineer",
company: "Tech Innovations",
companyUrl: "https://techinnovations.com",
description: "Architecting scalable microservices...",
logo: "/images/logos/tech-innovations.png",
projectLink: "/projects/microservices-platform",
smartDates: true
},
{
year: "Jun. 2021 - Dec. 2023",
title: "Full Stack Developer",
company: "Digital Agency",
companyUrl: "https://digitalagency.com",
description: "Built custom web applications for clients...",
logo: "/images/logos/digital-agency.png"
},
{
year: "Jan. 2020 - May 2021",
title: "Junior Developer",
company: "Startup XYZ",
description: "First professional role, learned a ton!",
// No logo or links
}
];
<Timeline items={fullTimeline} smartDates />
Education Timeline¶
const educationTimeline = [
{
year: "2024 - Present",
title: "Master's in Computer Science",
company: "University Name",
companyUrl: "https://university.edu",
description: "Focus on AI and Machine Learning",
logo: "/images/logos/university.png",
smartDates: true,
},
{
year: "2020 - 2024",
title: "Bachelor's in Software Engineering",
company: "College Name",
description: "Graduated with honors",
},
];
See Also¶
- Custom Components - Overview
- Next.js Image Component - Image optimization
- Date Formatting - JavaScript Date API
Next Steps¶
- Add Animations: Fade in timeline items on scroll
- Horizontal Timeline: Alternative layout for desktop
- Expandable Details: Click to expand/collapse descriptions
- Icons: Add role-specific icons (code, design, management)
- Duration Calculator: Show duration "2 years 4 months"
- Skills Tags: Display relevant skills for each position
- Hover Effects: Interactive hover states for timeline items
Last Updated: 2024-02-09
Related Docs: Components | Custom Components | Data Flow