maka:maka-frames

v0.2.7Published 4 weeks ago

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:

  1. The package is located in packages/maka-frames/
  2. Meteor will automatically detect and use it

Add to your app

meteor add maka:maka-frames

Dependencies

The package requires:

  • react and react-dom
  • meteor/accounts-base (for user profile storage)
  • meteor/mongo
  • meteor/mdg:validated-method
  • interactjs (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

PropTypeDefaultDescription
idStringrequiredUnique identifier for the frame
titleString'Change Me'Title displayed in the frame header
xNumber0X position in pixels
yNumber0Y position in pixels
widthNumber150Width in pixels
heightNumber150Height in pixels
displayBooleantrueWhether the frame is visible
zIndexNumber1Stack order of the frame
styleObject{}Additional CSS styles
minWidthNumber200Minimum width in pixels
minHeightNumber200Minimum height in pixels
onFrameUpdateFunction() => {}Callback when frame is updated
onContextMenuFunction() => {}Callback for context menu events

FrameManager Utility

Methods

toggleFrame(frameId, profile, options, openAndBringToFront)

Toggle a frame's visibility.

  • frameId (String): The frame ID
  • profile (Object): User profile object
  • options (Object): Additional options to store with the frame
  • openAndBringToFront (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

Using FrameManagerBar

The FrameManagerBar component provides a taskbar/dock showing all frames in your workspace. As of version 0.2.6+, it must be added manually to your application for maximum flexibility.

1import { Workspace, FrameManagerBar } from 'meteor/maka:maka-frames';
2
3const MyApp = () => {
4  return (
5    <Workspace workspaceId="demo-workspace" workspaceName="Demo">
6      {({ workspace, allWorkspaces, toggleFrame, onFrameManagerClick, onFrameManagerClose }) => (
7        <>
8          {/* Your frames */}
9          {workspace.frames
10            .filter(f => f.display)
11            .map(frame => (
12              <Frame key={frame.id} {...frame}>
13                <div>Frame content</div>
14              </Frame>
15            ))}
16
17          {/* Frame Manager Bar */}
18          <FrameManagerBar
19            workspace={workspace}
20            allWorkspaces={allWorkspaces}
21            onFrameClick={onFrameManagerClick}
22            onFrameClose={onFrameManagerClose}
23            position="bottom"
24            showClosed={true}
25            maxItems={20}
26          />
27        </>
28      )}
29    </Workspace>
30  );
31};

FrameManagerBar Props

PropTypeDefaultDescription
workspaceObjectrequiredCurrent workspace object
allWorkspacesArray[]Array of all workspaces (for workspace switching)
onFrameClickFunctionrequiredHandler when a frame is clicked
onFrameCloseFunction-Handler when a frame is closed
onWorkspaceClickFunction-Handler when a workspace tab is clicked
positionString'bottom'Position: 'top', 'bottom', 'left', 'right'
showClosedBooleantrueWhether to show closed frames
maxItemsNumber20Maximum number of items to display

Custom Positioning

Since FrameManagerBar is now a separate component, you can position it anywhere in your layout:

1<div className="my-app-layout">
2  <Workspace workspaceId="demo-workspace">
3    {(api) => (
4      <>
5        {/* Left sidebar with FrameManagerBar */}
6        <aside className="sidebar">
7          <FrameManagerBar
8            workspace={api.workspace}
9            allWorkspaces={api.allWorkspaces}
10            onFrameClick={api.onFrameManagerClick}
11            onFrameClose={api.onFrameManagerClose}
12            position="left"
13          />
14        </aside>
15
16        {/* Main content area with frames */}
17        <main className="content">
18          {/* Your frames here */}
19        </main>
20      </>
21    )}
22  </Workspace>
23</div>

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: