Skip to content

iamboliver/dynamic-product-timeline

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

10 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Dynamic Product Timeline

A beautiful, interactive horizontal timeline for showcasing product features, releases, and roadmaps. Built with React, TypeScript, and Framer Motion.

TypeScript React License

Demo

Demo

Demo


✨ Features

  • Drag to Navigate - Smooth horizontal scrolling with momentum physics
  • Smart Card Layout - Automatic collision avoidance prevents overlapping
  • Dynamic Month Widths - Optional mode expands dense months to fit more cards
  • Search Bar - Optional search to find and navigate to features instantly
  • Past & Future - Cards positioned above (future) or below (past) the timeline
  • Focus Detection - Card closest to center automatically highlights
  • Detail Modals - Click any card to see full details with media gallery
  • Like/Dislike Voting - Built-in voting system (localStorage or API-ready)
  • Dark & Light Themes - Toggle between themes with smooth transitions
  • Back to Today - Floating button to reset view when scrolled away
  • Fully Typed - Complete TypeScript support
  • JSON-Driven - Feed data via URL or inline props

πŸ“¦ Installation

# Clone the repository
git clone https://github.com/iamboliver/dynamic-product-timeline.git
cd dynamic-product-timeline

# Install dependencies
npm install

# Start development server
npm run dev

πŸš€ Quick Start

Basic Usage

import { FeatureTimeline } from './components/FeatureTimeline';

function App() {
  return <FeatureTimeline dataUrl="/features.json" />;
}

With Inline Data

import { FeatureTimeline } from './components/FeatureTimeline';

const features = [
  {
    id: '1',
    title: 'Dark Mode',
    description: 'Full dark theme support across the application.',
    releaseDate: '2024-06-15',
    status: 'released',
    tags: ['UI', 'Accessibility'],
  },
  {
    id: '2',
    title: 'AI Assistant',
    description: 'Intelligent assistant powered by machine learning.',
    releaseDate: '2025-03-01',
    status: 'planned',
    highlight: true,
  },
];

function App() {
  return <FeatureTimeline features={features} />;
}

πŸ“‹ Data Format

Create a features.json file in your public folder:

[
  {
    "id": "unique-id",
    "title": "Feature Name",
    "description": "A detailed description of the feature.",
    "releaseDate": "2025-01-15",
    "status": "released",
    "screenshots": [
      "https://example.com/screenshot1.png",
      "https://example.com/screenshot2.png"
    ],
    "videos": [
      "https://example.com/demo.mp4"
    ],
    "tags": ["Category", "Type"],
    "highlight": false
  }
]
Field Reference
Field Type Required Description
id string Yes Unique identifier
title string Yes Feature name
description string Yes Feature description
releaseDate string Yes ISO 8601 date (YYYY-MM-DD)
status string Yes released, beta, or planned
screenshots string[] No Array of image URLs
videos string[] No Array of video URLs
tags string[] No Category tags
highlight boolean No Emphasize this feature

🎨 Theming

The timeline supports dark and light themes out of the box.

Using the Theme Toggle

import { useState } from 'react';
import { ThemeProvider } from 'styled-components';
import { FeatureTimeline } from './components/FeatureTimeline';
import { ThemeToggle } from './components/ThemeToggle';
import { darkTheme, lightTheme } from './utils/constants';

function App() {
  const [isDark, setIsDark] = useState(true);

  return (
    <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
      <ThemeToggle isDark={isDark} onToggle={() => setIsDark(!isDark)} />
      <FeatureTimeline dataUrl="/features.json" />
    </ThemeProvider>
  );
}

Custom Theme

import { TimelineTheme } from './types';

const customTheme: TimelineTheme = {
  colors: {
    background: '#0a0a0a',
    backgroundElevated: '#141414',
    backgroundSurface: '#1a1a1a',
    primary: '#db0011',           // Accent color
    primaryGlow: 'rgba(219, 0, 17, 0.4)',
    textPrimary: '#ffffff',
    textSecondary: '#b3b3b3',
    greyLight: '#b3b3b3',
    greyMid: '#666666',
    greyDark: '#333333',
    statusReleased: '#22c55e',    // Green
    statusBeta: '#f59e0b',        // Amber
    statusPlanned: '#db0011',     // Red
    todayMarker: '#db0011',
  },
  spacing: {
    cardBorderRadius: 20,
    cardPadding: 16,
    stemLength: 40,
    baseYOffset: 100,
    slotHeight: 120,
    minCardSpacing: 200,
  },
  animation: {
    dragMomentum: true,
    dragElastic: 0.1,
    focusTransitionDuration: 200,
    entranceStaggerDelay: 100,
  },
};

πŸ—³οΈ Voting System

The timeline includes a like/dislike voting system that works out of the box with localStorage.

Connecting to a Backend

To persist votes across users, update src/services/voteService.ts:

// Set these URLs to enable API mode
const VOTES_API_URL = 'https://api.example.com/votes';      // GET: returns VotesMap
const VOTE_SUBMIT_URL = 'https://api.example.com/vote';     // POST: submit vote

Expected API format:

// GET /votes response
{
  "feature-id-1": { "likes": 42, "dislikes": 3 },
  "feature-id-2": { "likes": 15, "dislikes": 8 }
}

// POST /vote request body
{
  "featureId": "feature-id-1",
  "voteType": "like",           // or "dislike"
  "previousVote": "dislike"     // optional, if changing vote
}

// POST /vote response
{ "likes": 43, "dislikes": 2 }

βš™οΈ Props Reference

interface FeatureTimelineProps {
  dataUrl?: string;             // URL to fetch features JSON
  features?: Feature[];         // Inline feature data
  today?: Date;                 // Override "today" (default: new Date())
  pxPerDay?: number;            // Pixels per day spacing (default: 12)
  className?: string;           // Additional CSS class
  dynamicMonthWidths?: boolean; // Expand dense months to fit cards (default: false)
  searchEnabled?: boolean;      // Show search bar to find features (default: false)
}

Dynamic Month Widths

By default, the timeline uses a linear scale where each day has equal width. When you have many features in the same month, cards may overlap.

Enable dynamicMonthWidths to automatically expand months with more features:

<FeatureTimeline dataUrl="/features.json" dynamicMonthWidths />

With this enabled:

  • Months with more features become wider
  • Cards within dense months spread out horizontally
  • The date label updates correctly when dragging through variable-width months

Search Bar

Enable a search bar in the top-left corner to quickly find and navigate to features:

<FeatureTimeline dataUrl="/features.json" searchEnabled />

With this enabled:

  • Search box appears in the top-left corner
  • Searches feature titles, descriptions, and tags
  • Shows up to 5 matching results in a dropdown
  • Clicking a result smoothly scrolls to that card and opens its modal

πŸ“ Project Structure

src/
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ FeatureTimeline/
β”‚   β”‚   β”œβ”€β”€ index.ts              # Public exports
β”‚   β”‚   β”œβ”€β”€ FeatureTimeline.tsx   # Main container
β”‚   β”‚   β”œβ”€β”€ TimelineAxis.tsx      # Horizontal line + ticks
β”‚   β”‚   β”œβ”€β”€ TodayMarker.tsx       # Center marker
β”‚   β”‚   β”œβ”€β”€ FeatureCardsLayer.tsx # Card positioning
β”‚   β”‚   β”œβ”€β”€ FeatureCard.tsx       # Individual card
β”‚   β”‚   β”œβ”€β”€ ConnectorStem.tsx     # Card-to-axis connector
β”‚   β”‚   β”œβ”€β”€ FeatureModal.tsx      # Detail modal
β”‚   β”‚   └── styles.ts             # Styled components
β”‚   └── ThemeToggle.tsx           # Dark/light toggle
β”œβ”€β”€ hooks/
β”‚   β”œβ”€β”€ useFeatureData.ts         # Data loading & processing
β”‚   β”œβ”€β”€ useFocusedFeature.ts      # Focus detection
β”‚   └── useVotes.ts               # Voting state
β”œβ”€β”€ services/
β”‚   └── voteService.ts            # Vote API layer
β”œβ”€β”€ utils/
β”‚   β”œβ”€β”€ timeScale.ts              # Date-to-pixel math
β”‚   β”œβ”€β”€ collisionAvoidance.ts     # Card staggering
β”‚   └── constants.ts              # Theme defaults
β”œβ”€β”€ types/
β”‚   └── index.ts                  # TypeScript interfaces
β”œβ”€β”€ App.tsx
└── main.tsx

πŸ› οΈ Development

# Start dev server
npm run dev

# Type checking
npm run build

# Preview production build
npm run preview

πŸ“ How It Works

Coordinate System

  • x = 0 represents today
  • Past dates β†’ negative x values
  • Future dates β†’ positive x values
  • Formula: x = daysDifference Γ— pxPerDay

Card Positioning

  1. Hemisphere: Past features go below the line, future features go above
  2. Collision Avoidance: Cards within minCardSpacing pixels are vertically staggered
  3. Focus Detection: Card closest to viewport center receives focus styling

Drag Mechanics

  • Uses Framer Motion's pan gesture system
  • Spring physics for smooth deceleration
  • Bounded to prevent scrolling past first/last feature

πŸ”§ Requirements

  • Node.js 18+
  • React 18+
  • Modern browser with ES2020 support

πŸ“„ License

MIT License - feel free to use this in your own projects!


🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

About

A beautiful, interactive horizontal timeline for showcasing product features, releases, and roadmaps. Built with React, TypeScript, and Framer Motion.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors