maka:maka-frames
A reusable Meteor package that provides a draggable, resizable frame system for building windowed interfaces. Built with React and interact.js.
Features
- Draggable Windows - Click and drag frames by the title bar
- Resizable Panels - Resize from all edges and corners
- Persistent State - Frame positions automatically saved to user profile in MongoDB
- Z-Index Management - Clicking brings frames to front
- Hide/Show - Toggle frame visibility
- Customizable - Configure minimum sizes, styling, and behavior
- React Integration - Full Meteor + React support
Installation
Local Package (Development)
This package is currently set up as a local package. To use it:
- The package is located in
packages/maka-frames/ - Meteor will automatically detect and use it
Add to your app
meteor add maka:maka-frames
Dependencies
The package requires:
reactandreact-dommeteor/accounts-base(for user profile storage)meteor/mongometeor/mdg:validated-methodinteractjs(installed automatically via npm)
Usage
Basic Example
1import React from 'react'; 2import { Frame, FrameManager } from 'meteor/maka:maka-frames'; 3import { withTracker } from 'meteor/react-meteor-data'; 4 5const MyComponent = ({ profile }) => { 6 const frames = profile.appState?.frames || []; 7 const mapFrame = frames.find(({ id }) => id === 'mapFrame'); 8 9 return ( 10 <div> 11 {mapFrame && ( 12 <Frame 13 id="mapFrame" 14 title="Map" 15 x={mapFrame.x || 100} 16 y={mapFrame.y || 100} 17 width={mapFrame.width || 400} 18 height={mapFrame.height || 300} 19 display={mapFrame.display} 20 zIndex={mapFrame.zIndex} 21 > 22 <div>Your content here</div> 23 </Frame> 24 )} 25 </div> 26 ); 27}; 28 29export default withTracker(() => { 30 const user = Meteor.user(); 31 const profile = user?.profile || { appState: { frames: [] } }; 32 return { profile }; 33})(MyComponent);
Toggle Frame Visibility
1import { FrameManager } from 'meteor/maka:maka-frames'; 2 3// Toggle a frame on/off 4const handleToggleMap = () => { 5 const user = Meteor.user(); 6 FrameManager.toggleFrame('mapFrame', user.profile); 7}; 8 9// Open a frame and bring to front 10const handleOpenMap = () => { 11 const user = Meteor.user(); 12 FrameManager.openFrame('mapFrame', user.profile, { someOption: 'value' }); 13}; 14 15// Close a frame 16const handleCloseMap = () => { 17 const user = Meteor.user(); 18 FrameManager.closeFrame('mapFrame', user.profile); 19};
API Reference
<Frame> Component
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | String | required | Unique identifier for the frame |
title | String | 'Change Me' | Title displayed in the frame header |
x | Number | 0 | X position in pixels |
y | Number | 0 | Y position in pixels |
width | Number | 150 | Width in pixels |
height | Number | 150 | Height in pixels |
display | Boolean | true | Whether the frame is visible |
zIndex | Number | 1 | Stack order of the frame |
style | Object | {} | Additional CSS styles |
minWidth | Number | 200 | Minimum width in pixels |
minHeight | Number | 200 | Minimum height in pixels |
onFrameUpdate | Function | () => {} | Callback when frame is updated |
onContextMenu | Function | () => {} | Callback for context menu events |
FrameManager Utility
Methods
toggleFrame(frameId, profile, options, openAndBringToFront)
Toggle a frame's visibility.
frameId(String): The frame IDprofile(Object): User profile objectoptions(Object): Additional options to store with the frameopenAndBringToFront(Boolean): If true, ensures frame is visible and on top
1FrameManager.toggleFrame('myFrame', user.profile, { data: 'example' }, false);
openFrame(frameId, profile, options)
Open a frame and bring it to the front.
1FrameManager.openFrame('myFrame', user.profile, { data: 'example' });
closeFrame(frameId, profile)
Close a frame (hide it).
1FrameManager.closeFrame('myFrame', user.profile);
bringToFront(frameId, profile)
Bring a frame to the front without toggling visibility.
1FrameManager.bringToFront('myFrame', user.profile);
resetFrames()
Reset all frame positions for the current user.
1FrameManager.resetFrames();
getFrames(callback)
Get all frames for the current user.
1FrameManager.getFrames((error, frames) => { 2 if (!error) { 3 console.log('User frames:', frames); 4 } 5});
Server Methods
The package provides three Meteor methods:
maka-frames.set-frame-position
Set or update a frame's position and properties.
1Meteor.call('maka-frames.set-frame-position', { 2 id: 'myFrame', 3 x: 100, 4 y: 100, 5 width: 400, 6 height: 300, 7 display: true, 8 zIndex: 1000 9});
maka-frames.reset-frame-positions
Reset all frames for the current user.
1Meteor.call('maka-frames.reset-frame-positions');
maka-frames.get-frames
Get all frames for the current user.
1Meteor.call('maka-frames.get-frames', (error, frames) => { 2 console.log(frames); 3});
Data Structure
Frame data is stored in the user profile:
1{ 2 _id: "userId", 3 profile: { 4 appState: { 5 frames: [ 6 { 7 id: "mapFrame", 8 title: "Map", 9 x: 100, 10 y: 100, 11 width: 400, 12 height: 300, 13 display: true, 14 zIndex: 1000, 15 options: { /* custom data */ } 16 } 17 ] 18 } 19 } 20}
Styling
The package includes default styling via frame.css. You can override styles by targeting these classes:
.frame-component- Main frame container.frame-component-title- Title bar.frame-component-content- Content area.frame-component-controls- Control buttons area.frame-component-close- Close button.frame-component-menu- Menu button- Resize handles:
.fc-resize-top,.fc-resize-bottom,.fc-resize-left,.fc-resize-right
Custom Styling Example
1.frame-component { 2 background-color: #1a1a1a; 3 border: 2px solid #333; 4} 5 6.frame-component-title { 7 background-color: #2a2a2a; 8 color: #fff; 9 font-weight: bold; 10}
Advanced Usage
Context Menu Support
1<Frame 2 id="myFrame" 3 title="My Frame" 4 onContextMenu={(event, frameId) => { 5 console.log('Right-clicked on frame:', frameId); 6 // Show custom context menu 7 }} 8> 9 <div>Content</div> 10</Frame>
Responsive Content
The frame automatically triggers window resize events when resized, allowing child components to respond:
1import React, { useEffect, useState } from 'react'; 2 3const ResponsiveContent = () => { 4 const [size, setSize] = useState({ width: 0, height: 0 }); 5 6 useEffect(() => { 7 const handleResize = () => { 8 const container = document.getElementById('myFrame'); 9 if (container) { 10 setSize({ 11 width: container.clientWidth, 12 height: container.clientHeight 13 }); 14 } 15 }; 16 17 window.addEventListener('resize', handleResize); 18 handleResize(); 19 20 return () => window.removeEventListener('resize', handleResize); 21 }, []); 22 23 return <div>Size: {size.width}x{size.height}</div>; 24};
Browser Compatibility
- Modern browsers (Chrome, Firefox, Safari, Edge)
- Mobile touch support via interact.js
License
MIT
Contributing
Contributions are welcome! Please submit issues and pull requests to the repository.
Credits
Built with: