@denisbitter/bitter-button-menu
v1.0.0
Published
A beautiful radial/orbit menu button component with smooth animations
Downloads
16
Maintainers
Readme
@denisbitter/bitter-button-menu
A beautiful, customizable radial/orbit menu button component for React applications with smooth animations powered by Framer Motion.
Features
- 🎨 Highly Customizable: Full control over colors, sizes, animations, and behavior
- 🌀 Radial Menu Layout: Items arranged in a circle around the main button
- ✨ Smooth Animations: Beautiful spring animations powered by Framer Motion
- 📱 Responsive: Works on all screen sizes
- 🎯 TypeScript Support: Full type definitions included
- 🔧 Easy Integration: Works seamlessly with React Router
- 🎭 Submenu Support: Nested menu capability
- 💾 Smart Persistence: Tooltip state saved in localStorage
- 🎪 Scroll Behavior: Button follows scroll with configurable behavior
Installation
npm install @denisbitter/bitter-button-menuor
yarn add @denisbitter/bitter-button-menuBasic Usage
import React from 'react';
import BitterButtonWithMenu from '@denisbitter/bitter-button-menu';
function App() {
const mainMenuItems = [
{
id: "home",
label: "Home",
angle: 0,
route: "/",
},
{
id: "about",
label: "About",
angle: -90,
route: "/about",
},
{
id: "contact",
label: "Contact",
angle: -180,
route: "/contact",
}
];
return (
<BitterButtonWithMenu
logoSrc="/path/to/your/logo.png"
logoAlt="Menu"
mainMenuItems={mainMenuItems}
/>
);
}
export default App;Advanced Usage
With Submenus
import React from 'react';
import BitterButtonWithMenu from '@denisbitter/bitter-button-menu';
function App() {
const mainMenuItems = [
{
id: "home",
label: "Home",
angle: 0,
route: "/",
},
{
id: "about",
label: "About",
angle: -90,
action: "openSubmenu",
submenu: "about",
hasSubmenu: true,
}
];
const submenuItems = {
about: [
{
id: "back",
label: "Back",
angle: 0,
action: "closeSubmenu",
isBack: true,
},
{
id: "team",
label: "Team",
angle: -60,
route: "/about/team",
},
{
id: "history",
label: "History",
angle: -120,
route: "/about/history",
}
]
};
return (
<BitterButtonWithMenu
logoSrc="/path/to/your/logo.png"
mainMenuItems={mainMenuItems}
submenuItems={submenuItems}
/>
);
}With Custom Configuration
import React from 'react';
import BitterButtonWithMenu from '@denisbitter/bitter-button-menu';
function App() {
const customConfig = {
visual: {
radius: 120,
button: {
width: 70,
height: 70,
},
colors: {
backdrop: "rgba(0, 0, 0, 0.3)",
},
},
animation: {
menuItem: {
stiffness: 300,
damping: 25,
staggerDelay: 0.08,
},
},
};
const mainMenuItems = [
// ... your menu items
];
return (
<BitterButtonWithMenu
logoSrc="/path/to/your/logo.png"
mainMenuItems={mainMenuItems}
config={customConfig}
accentColor="#FF6B35"
tooltipText="Open Menu"
tooltipDuration={5000}
/>
);
}With Dynamic Name Context
import React from 'react';
import BitterButtonWithMenu from '@denisbitter/bitter-button-menu';
function App() {
const mainMenuItems = [
{
id: "home",
label: "Hello {name}",
angle: 0,
route: "/",
dynamic: true,
}
];
const nameContext = {
name: "John"
};
return (
<BitterButtonWithMenu
logoSrc="/path/to/your/logo.png"
mainMenuItems={mainMenuItems}
nameContext={nameContext}
/>
);
}Props
BitterButtonWithMenu
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| logoSrc | string | - | URL/path to the logo image |
| logoAlt | string | "Menu Button" | Alt text for the logo |
| mainMenuItems | Array | [] | Array of main menu item configurations |
| submenuItems | Object | {} | Object with submenu configurations (key: submenu id) |
| onMenuClick | Function | - | Callback when menu is toggled |
| onOverlayOpen | Function | - | Callback when overlay action is triggered |
| config | Object | default config | Custom configuration object |
| nameContext | Object | - | Context for dynamic name replacement |
| tooltipText | string | "Menü öffnen" | Text to show in tooltip |
| tooltipDuration | number | 6000 | Duration tooltip stays visible (ms) |
| accentColor | string | "#AC8E66" | Accent color for highlights |
Menu Item Configuration
{
id: string; // Unique identifier
label: string; // Display text
angle: number; // Position angle (-180 to 180)
route?: string; // React Router route
action?: string; // Action type: 'openOverlay', 'openSubmenu', 'closeSubmenu'
submenu?: string; // Submenu ID (if action is 'openSubmenu')
hasSubmenu?: boolean; // Indicates item has submenu
dynamic?: boolean; // Enable dynamic label replacement
isMainMenu?: boolean; // Style as main menu item
isBack?: boolean; // Style as back button
onClick?: Function; // Custom click handler
}Configuration Object
{
visual: {
radius: number; // Radius of menu circle
menuOffset: number; // Offset when menu opens
colors: {
primary: string;
primaryDark: string;
background: string;
backgroundDark: string;
text: string;
border: string;
borderHighlight: string;
backdrop: string;
};
button: {
width: number;
height: number;
fontSize: string;
fontFamily: string;
};
backdrop: {
blur: string;
opacity: number;
};
};
animation: {
menuItem: {
stiffness: number;
damping: number;
staggerDelay: number;
};
backdrop: {
duration: number;
};
};
}Hooks
useOrbitMenuConfig
Access and customize the menu configuration at runtime.
import { useOrbitMenuConfig } from '@denisbitter/bitter-button-menu';
function CustomMenuControls() {
const { config, updateConfig, applyPreset, resetConfig } = useOrbitMenuConfig();
return (
<div>
<button onClick={() => updateConfig('visual.radius', 150)}>
Increase Radius
</button>
<button onClick={() => applyPreset('compact')}>
Apply Compact Preset
</button>
<button onClick={resetConfig}>
Reset to Default
</button>
</div>
);
}Presets
Available configuration presets:
compact- Smaller radius and faster animationsspacious- Larger radius and slower animationsfast- Quick, snappy animationssmooth- Slower, smoother animations
Styling
The component uses Tailwind CSS classes. Make sure you have Tailwind CSS configured in your project, or the styles won't work properly.
Tailwind Configuration
Add to your tailwind.config.js:
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
"./node_modules/@denisbitter/bitter-button-menu/dist/**/*.js"
],
// ... rest of your config
}Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Requirements
- React 18+
- React Router DOM 6+
- Framer Motion 11+
License
MIT © Denis Bitter
Author
Denis Bitter
- Email: [email protected]
- GitHub: @THEORIGINALBITTER
- LinkedIn: denisbitter
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
If you encounter any issues or have questions, please file an issue on the GitHub repository.
