diff --git a/skins/base/views/molecules/ContextualMenu.js b/skins/base/views/molecules/ContextualMenu.js
new file mode 100644
index 0000000000..58c542ee6b
--- /dev/null
+++ b/skins/base/views/molecules/ContextualMenu.js
@@ -0,0 +1,35 @@
+/*
+Copyright 2015 OpenMarket Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict';
+
+var React = require('react');
+var classNames = require('classnames');
+
+var dis = require("../../../../src/dispatcher");
+
+var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
+
+module.exports = React.createClass({
+ displayName: 'ContextualMenu',
+
+ render: function() {
+ return (
+
+
+ );
+ }
+});
diff --git a/src/ContextualMenu.js b/src/ContextualMenu.js
new file mode 100644
index 0000000000..cabab0c375
--- /dev/null
+++ b/src/ContextualMenu.js
@@ -0,0 +1,72 @@
+/*
+Copyright 2015 OpenMarket Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+
+'use strict';
+
+var React = require('react');
+var q = require('q');
+
+// Shamelessly ripped off Modal.js. There's probably a better way
+// of doing reusable widgets like dialog boxes & menus where we go and
+// pass in a custom control as the actual body.
+
+module.exports = {
+ ContextualMenuContainerId: "mx_ContextualMenu_Container",
+
+ getOrCreateContainer: function() {
+ var container = document.getElementById(this.ContextualMenuContainerId);
+
+ if (!container) {
+ container = document.createElement("div");
+ container.id = this.ContextualMenuContainerId;
+ document.body.appendChild(container);
+ }
+
+ return container;
+ },
+
+ createMenu: function (Element, props) {
+ var self = this;
+
+ var closeMenu = function() {
+ React.unmountComponentAtNode(self.getOrCreateContainer());
+
+ if (props && props.onFinished) props.onFinished.apply(null, arguments);
+ };
+
+ var position = {
+ top: props.top - 20,
+ right: props.right + 8,
+ };
+
+ // FIXME: If a menu uses getDefaultProps it clobbers the onFinished
+ // property set here so you can't close the menu from a button click!
+ var menu = (
+
+
+
+
+
+
+
+ );
+
+ React.render(menu, this.getOrCreateContainer());
+
+ return {close: closeMenu};
+ },
+};