mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-22 09:37:17 +00:00
Convert ContextMenu and ContextMenuTarget to typescriptThis is the first component to use a connected component
This commit is contained in:
parent
f20830c247
commit
1c05bd7b7a
4 changed files with 77 additions and 35 deletions
|
|
@ -1,35 +1,47 @@
|
|||
import invariant from "invariant";
|
||||
import React from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { connect } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
import classnames from "classnames";
|
||||
|
||||
import "../../css/context-menu.css";
|
||||
import { AppState } from "../types";
|
||||
|
||||
interface PortalProps {
|
||||
zIndex: number;
|
||||
top: number;
|
||||
left: number;
|
||||
}
|
||||
|
||||
class Portal extends React.Component<PortalProps> {
|
||||
_node?: HTMLDivElement;
|
||||
|
||||
class Portal extends React.Component {
|
||||
componentWillMount() {
|
||||
this._node = document.createElement("div");
|
||||
this._node.id = "webamp-context-menu";
|
||||
this._node.style.position = "absolute";
|
||||
this._node.style.top = 0;
|
||||
this._node.style.left = 0;
|
||||
this._node.style.zIndex = this.props.zIndex + 1;
|
||||
this._node.style.top = "0";
|
||||
this._node.style.left = "0";
|
||||
this._node.style.zIndex = String(this.props.zIndex + 1);
|
||||
document.body.appendChild(this._node);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.body.removeChild(this._node);
|
||||
if (this._node) {
|
||||
document.body.removeChild(this._node);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = {
|
||||
position: "absolute",
|
||||
top: this.props.top,
|
||||
left: this.props.left
|
||||
top: String(this.props.top),
|
||||
left: String(this.props.left),
|
||||
// WTF Typescript. There's got to be a better way.
|
||||
position: "absolute" as "absolute"
|
||||
};
|
||||
return createPortal(
|
||||
<div style={style}>{this.props.children}</div>,
|
||||
this._node
|
||||
this._node!
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -40,25 +52,36 @@ export const Hr = () => (
|
|||
</li>
|
||||
);
|
||||
|
||||
export const Parent = ({ children, label }) => (
|
||||
interface ParentProps {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Parent = ({ children, label }: ParentProps) => (
|
||||
<li className="parent">
|
||||
<ul>{children}</ul>
|
||||
{label}
|
||||
</li>
|
||||
);
|
||||
|
||||
export const LinkNode = props => (
|
||||
interface LinkNodeProps {
|
||||
label: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export const LinkNode = (props: LinkNodeProps) => (
|
||||
<li>
|
||||
<a {...props}>{props.label}</a>
|
||||
</li>
|
||||
);
|
||||
|
||||
LinkNode.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
href: PropTypes.string.isRequired
|
||||
};
|
||||
interface NodeProps {
|
||||
label: string;
|
||||
checked: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Node = props => {
|
||||
export const Node = (props: NodeProps) => {
|
||||
const { label, checked, className = "", ...passThroughProps } = props;
|
||||
return (
|
||||
<li className={classnames(className, { checked })} {...passThroughProps}>
|
||||
|
|
@ -67,12 +90,17 @@ export const Node = props => {
|
|||
);
|
||||
};
|
||||
|
||||
Node.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
hotkey: PropTypes.string
|
||||
};
|
||||
interface ContextMenuProps {
|
||||
children: React.ReactNode;
|
||||
offsetTop: number;
|
||||
offsetLeft: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
selected: boolean;
|
||||
zIndex: number;
|
||||
}
|
||||
|
||||
class ContextMenu extends React.Component {
|
||||
class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
|
|
@ -95,7 +123,7 @@ class ContextMenu extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
zIndex: state.display.zIndex
|
||||
});
|
||||
|
||||
|
|
@ -1,9 +1,20 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import ContextMenu from "./ContextMenu";
|
||||
|
||||
export default class ContextMenuTarget extends React.Component {
|
||||
constructor(props) {
|
||||
interface Props {
|
||||
handle: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
top: number;
|
||||
bottom: number;
|
||||
}
|
||||
interface State {
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
export default class ContextMenuTarget extends React.Component<Props, State> {
|
||||
handleNode?: HTMLDivElement | null;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { selected: false };
|
||||
}
|
||||
|
|
@ -20,13 +31,15 @@ export default class ContextMenuTarget extends React.Component {
|
|||
this.setState({ selected: !this.state.selected });
|
||||
};
|
||||
|
||||
_handleGlobalClick = e => {
|
||||
_handleGlobalClick = (e: MouseEvent) => {
|
||||
if (
|
||||
// Typescript does not believe that these click events are always fired on DOM nodes.
|
||||
e.target instanceof Element &&
|
||||
this.state.selected &&
|
||||
// Not sure how, but it's possible for this to get called when handleNode is null/undefined.
|
||||
// https://sentry.io/share/issue/2066cd79f21e4f279791319f4d2ea35d/
|
||||
this.handleNode &&
|
||||
!this.handleNode.contains(e.target)
|
||||
!this.handleNode.contains(e.target!)
|
||||
) {
|
||||
this.setState({ selected: false });
|
||||
}
|
||||
|
|
@ -70,10 +83,3 @@ export default class ContextMenuTarget extends React.Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
ContextMenuTarget.propTypes = {
|
||||
children: PropTypes.any.isRequired,
|
||||
handle: PropTypes.any.isRequired,
|
||||
top: PropTypes.bool,
|
||||
bottom: PropTypes.bool
|
||||
};
|
||||
|
|
@ -61,6 +61,7 @@
|
|||
"@types/classnames": "^2.2.6",
|
||||
"@types/invariant": "^2.2.29",
|
||||
"@types/react": "^16.4.14",
|
||||
"@types/react-dom": "^16.0.7",
|
||||
"@types/react-redux": "^6.0.8",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "^9.0.0-beta.3",
|
||||
|
|
|
|||
|
|
@ -739,6 +739,13 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-dom@^16.0.7":
|
||||
version "16.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.7.tgz#54d0f867a76b90597e8432030d297982f25c20ba"
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-redux@^6.0.8":
|
||||
version "6.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-6.0.8.tgz#29c884174516f0cfab6638d2236fd7d53f45640e"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue