DEV Community

William Baker
William Baker

Posted on

Building Circular Navigation Menus with react-planet in React

React Planet is a library for creating circular, orbital navigation menus in React applications. It provides a unique way to display menu items in a circular arrangement around a central button, creating an engaging and space-efficient navigation experience. This guide walks through setting up and creating circular navigation menus using React Planet with React, from installation to a working implementation. This is part 44 of a series on using React Planet with React.

Prerequisites

Before you begin, make sure you have:

  • Node.js version 14.0 or higher installed
  • npm, yarn, or pnpm package manager
  • A React project (version 16.8 or higher) or create-react-app setup
  • Basic knowledge of React hooks (useState)
  • Familiarity with JavaScript/TypeScript
  • Understanding of CSS for styling

Installation

Install React Planet using your preferred package manager:

npm install react-planet
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add react-planet
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add react-planet
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "react-planet": "^1.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

React Planet requires minimal setup. Import the component and you're ready to use it.

First Example / Basic Usage

Let's create a simple circular menu. Create a new file src/PlanetMenuExample.jsx:

// src/PlanetMenuExample.jsx
import React from 'react';
import { Planet } from 'react-planet';

function PlanetMenuExample() {
  const menuItems = [
    { id: 1, content: '🏠', tooltip: 'Home' },
    { id: 2, content: 'ℹ️', tooltip: 'About' },
    { id: 3, content: '⚙️', tooltip: 'Settings' },
    { id: 4, content: '📧', tooltip: 'Contact' }
  ];

  return (
    <div style={{ 
      padding: '50px', 
      display: 'flex', 
      justifyContent: 'center', 
      alignItems: 'center',
      minHeight: '400px'
    }}>
      <Planet
        centerContent={<div style={{
          width: '60px',
          height: '60px',
          borderRadius: '50%',
          backgroundColor: '#007bff',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          color: 'white',
          fontSize: '24px',
          cursor: 'pointer',
          boxShadow: '0 4px 6px rgba(0,0,0,0.1)'
        }}></div>}
        hideOrbit
        open
      >
        {menuItems.map(item => (
          <div
            key={item.id}
            style={{
              width: '50px',
              height: '50px',
              borderRadius: '50%',
              backgroundColor: '#28a745',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              fontSize: '24px',
              cursor: 'pointer',
              boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
              transition: 'transform 0.2s'
            }}
            title={item.tooltip}
            onClick={() => alert(`Clicked ${item.tooltip}`)}
          >
            {item.content}
          </div>
        ))}
      </Planet>
    </div>
  );
}

export default PlanetMenuExample;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import PlanetMenuExample from './PlanetMenuExample';
import './App.css';

function App() {
  return (
    <div className="App">
      <PlanetMenuExample />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This creates a basic circular menu with items arranged around a central button.

Understanding the Basics

React Planet provides several key features:

  • Planet component: The main component that creates the circular menu
  • centerContent: The central button/content that triggers the menu
  • Orbital items: Menu items that orbit around the center
  • Animation: Smooth animations for opening and closing
  • Customization: Extensive styling and positioning options

Key concepts:

  • Center Content: The main button or content in the center of the circle
  • Orbital Items: Menu items that appear around the center in a circular pattern
  • Open State: Control whether the menu is open or closed
  • Positioning: Items are automatically positioned in a circle
  • Styling: Customize the appearance of both center and orbital items

Here's an example with toggle functionality:

// src/TogglePlanetMenu.jsx
import React, { useState } from 'react';
import { Planet } from 'react-planet';

function TogglePlanetMenu() {
  const [isOpen, setIsOpen] = useState(false);

  const menuItems = [
    { id: 1, content: '🏠', label: 'Home', color: '#007bff' },
    { id: 2, content: '📊', label: 'Dashboard', color: '#28a745' },
    { id: 3, content: '⚙️', label: 'Settings', color: '#ffc107' },
    { id: 4, content: '👤', label: 'Profile', color: '#dc3545' },
    { id: 5, content: '📧', label: 'Messages', color: '#6c757d' }
  ];

  return (
    <div style={{ 
      padding: '50px', 
      display: 'flex', 
      justifyContent: 'center', 
      alignItems: 'center',
      minHeight: '500px'
    }}>
      <Planet
        centerContent={
          <div
            onClick={() => setIsOpen(!isOpen)}
            style={{
              width: '70px',
              height: '70px',
              borderRadius: '50%',
              backgroundColor: isOpen ? '#dc3545' : '#007bff',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              color: 'white',
              fontSize: '28px',
              cursor: 'pointer',
              boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
              transition: 'all 0.3s',
              transform: isOpen ? 'rotate(45deg)' : 'rotate(0deg)'
            }}
          >
            {isOpen ? '' : ''}
          </div>
        }
        hideOrbit
        open={isOpen}
      >
        {menuItems.map(item => (
          <div
            key={item.id}
            style={{
              width: '60px',
              height: '60px',
              borderRadius: '50%',
              backgroundColor: item.color,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              fontSize: '24px',
              cursor: 'pointer',
              boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
              transition: 'transform 0.2s',
              position: 'relative'
            }}
            title={item.label}
            onClick={() => {
              alert(`Clicked ${item.label}`);
              setIsOpen(false);
            }}
            onMouseEnter={(e) => e.target.style.transform = 'scale(1.1)'}
            onMouseLeave={(e) => e.target.style.transform = 'scale(1)'}
          >
            {item.content}
          </div>
        ))}
      </Planet>
    </div>
  );
}

export default TogglePlanetMenu;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a comprehensive navigation system with a circular menu:

// src/CircularNavigation.jsx
import React, { useState } from 'react';
import { Planet } from 'react-planet';

function CircularNavigation() {
  const [isOpen, setIsOpen] = useState(false);
  const [activeItem, setActiveItem] = useState(null);

  const menuItems = [
    { 
      id: 1, 
      icon: '🏠', 
      label: 'Home', 
      color: '#007bff',
      action: () => console.log('Navigate to Home')
    },
    { 
      id: 2, 
      icon: '📊', 
      label: 'Analytics', 
      color: '#28a745',
      action: () => console.log('Navigate to Analytics')
    },
    { 
      id: 3, 
      icon: '📁', 
      label: 'Files', 
      color: '#ffc107',
      action: () => console.log('Navigate to Files')
    },
    { 
      id: 4, 
      icon: '⚙️', 
      label: 'Settings', 
      color: '#dc3545',
      action: () => console.log('Navigate to Settings')
    },
    { 
      id: 5, 
      icon: '👤', 
      label: 'Profile', 
      color: '#6c757d',
      action: () => console.log('Navigate to Profile')
    },
    { 
      id: 6, 
      icon: '📧', 
      label: 'Messages', 
      color: '#17a2b8',
      action: () => console.log('Navigate to Messages')
    }
  ];

  const handleItemClick = (item) => {
    setActiveItem(item.id);
    item.action();
    setIsOpen(false);
  };

  return (
    <div style={{ 
      padding: '50px', 
      display: 'flex', 
      flexDirection: 'column',
      alignItems: 'center',
      minHeight: '600px',
      backgroundColor: '#f8f9fa'
    }}>
      <h1 style={{ marginBottom: '40px' }}>Circular Navigation Menu</h1>

      <Planet
        centerContent={
          <div
            onClick={() => setIsOpen(!isOpen)}
            style={{
              width: '80px',
              height: '80px',
              borderRadius: '50%',
              background: isOpen 
                ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
                : 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              color: 'white',
              fontSize: '32px',
              cursor: 'pointer',
              boxShadow: '0 6px 12px rgba(0,0,0,0.15)',
              transition: 'all 0.3s',
              transform: isOpen ? 'rotate(90deg)' : 'rotate(0deg)'
            }}
          >
            {isOpen ? '' : ''}
          </div>
        }
        hideOrbit
        open={isOpen}
        orbitRadius={120}
      >
        {menuItems.map(item => (
          <div
            key={item.id}
            onClick={() => handleItemClick(item)}
            style={{
              width: '70px',
              height: '70px',
              borderRadius: '50%',
              backgroundColor: activeItem === item.id ? '#fff' : item.color,
              border: activeItem === item.id ? `4px solid ${item.color}` : 'none',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              fontSize: '28px',
              cursor: 'pointer',
              boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
              transition: 'all 0.3s',
              position: 'relative'
            }}
            title={item.label}
            onMouseEnter={(e) => {
              if (activeItem !== item.id) {
                e.target.style.transform = 'scale(1.15)';
                e.target.style.boxShadow = '0 6px 12px rgba(0,0,0,0.3)';
              }
            }}
            onMouseLeave={(e) => {
              if (activeItem !== item.id) {
                e.target.style.transform = 'scale(1)';
                e.target.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
              }
            }}
          >
            {item.icon}
            {activeItem === item.id && (
              <div style={{
                position: 'absolute',
                bottom: '-30px',
                left: '50%',
                transform: 'translateX(-50%)',
                backgroundColor: '#333',
                color: 'white',
                padding: '4px 8px',
                borderRadius: '4px',
                fontSize: '12px',
                whiteSpace: 'nowrap'
              }}>
                {item.label}
              </div>
            )}
          </div>
        ))}
      </Planet>

      {activeItem && (
        <div style={{
          marginTop: '40px',
          padding: '20px',
          backgroundColor: 'white',
          borderRadius: '8px',
          boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
        }}>
          <p>Active: {menuItems.find(item => item.id === activeItem)?.label}</p>
        </div>
      )}
    </div>
  );
}

export default CircularNavigation;
Enter fullscreen mode Exit fullscreen mode

Now create a floating action button style menu:

// src/FloatingActionMenu.jsx
import React, { useState } from 'react';
import { Planet } from 'react-planet';

function FloatingActionMenu() {
  const [isOpen, setIsOpen] = useState(false);

  const quickActions = [
    { id: 1, icon: '', label: 'Add', color: '#28a745' },
    { id: 2, icon: '✏️', label: 'Edit', color: '#007bff' },
    { id: 3, icon: '📤', label: 'Share', color: '#ffc107' },
    { id: 4, icon: '🗑️', label: 'Delete', color: '#dc3545' }
  ];

  return (
    <div style={{
      position: 'fixed',
      bottom: '30px',
      right: '30px',
      zIndex: 1000
    }}>
      <Planet
        centerContent={
          <div
            onClick={() => setIsOpen(!isOpen)}
            style={{
              width: '60px',
              height: '60px',
              borderRadius: '50%',
              backgroundColor: '#007bff',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              color: 'white',
              fontSize: '24px',
              cursor: 'pointer',
              boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
              transition: 'all 0.3s',
              transform: isOpen ? 'rotate(45deg)' : 'rotate(0deg)'
            }}
          >
            {isOpen ? '' : ''}
          </div>
        }
        hideOrbit
        open={isOpen}
        orbitRadius={80}
      >
        {quickActions.map(action => (
          <div
            key={action.id}
            style={{
              width: '50px',
              height: '50px',
              borderRadius: '50%',
              backgroundColor: action.color,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              fontSize: '20px',
              cursor: 'pointer',
              boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
              transition: 'transform 0.2s'
            }}
            title={action.label}
            onClick={() => {
              alert(`Action: ${action.label}`);
              setIsOpen(false);
            }}
            onMouseEnter={(e) => e.target.style.transform = 'scale(1.1)'}
            onMouseLeave={(e) => e.target.style.transform = 'scale(1)'}
          >
            {action.icon}
          </div>
        ))}
      </Planet>
    </div>
  );
}

export default FloatingActionMenu;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import CircularNavigation from './CircularNavigation';
import FloatingActionMenu from './FloatingActionMenu';
import './App.css';

function App() {
  return (
    <div className="App">
      <CircularNavigation />
      <FloatingActionMenu />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • Circular navigation menu with toggle
  • Active item highlighting
  • Hover effects and animations
  • Floating action button style
  • Multiple menu items in circular arrangement
  • Custom styling and theming

Common Issues / Troubleshooting

  1. Menu items not appearing: Make sure the open prop is set to true and the Planet component is properly rendered. Check that menu items are passed as children.

  2. Items not positioned correctly: Adjust the orbitRadius prop to control the distance of items from the center. Larger values create a bigger circle.

  3. Animation not working: Ensure you're managing the open state correctly. The menu animates when the open prop changes.

  4. Styling conflicts: If items don't look right, check for CSS conflicts. Use inline styles or CSS classes to customize the appearance of both center and orbital items.

  5. Menu not closing: Make sure you have a way to set open to false. This can be done by clicking the center button again or clicking on a menu item.

  6. Items overlapping: Adjust the orbitRadius or reduce the number of items if they're too close together. You can also adjust the size of individual items.

Next Steps

Now that you have an understanding of React Planet:

  • Explore advanced positioning and animation options
  • Learn about custom orbit configurations
  • Implement keyboard navigation for accessibility
  • Add custom animations and transitions
  • Integrate with React Router for navigation
  • Learn about other circular menu libraries
  • Check the official repository: https://github.com/innFactory/react-planet
  • Look for part 45 of this series for more advanced topics

Summary

You've successfully set up React Planet in your React application and created circular navigation menus with toggle functionality and custom styling. React Planet provides a unique and engaging way to display menu items in a circular arrangement.

SEO Keywords

react-planet
React circular menu
react-planet tutorial
React orbital navigation
react-planet installation
React circular navigation menu
react-planet example
React planet menu
react-planet setup
React circular UI
react-planet customization
React navigation component
react-planet animations
React floating menu
react-planet getting started

Top comments (0)