Skip to content

Instantly share code, notes, and snippets.

@codecorsair
Last active February 1, 2017 23:12
Show Gist options
  • Save codecorsair/bae95301431b3180e6fad9aa3ccec8cf to your computer and use it in GitHub Desktop.
Save codecorsair/bae95301431b3180e6fad9aa3ccec8cf to your computer and use it in GitHub Desktop.
React TypeScript confirm dialog component.
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* @Author: JB (jb@codecorsair.com)
* @Date: 2017-02-01 14:43:06
* @Last Modified by: JB (jb@codecorsair.com)
* @Last Modified time: 2017-02-01 17:49:37
*/
/*
* Usage:
*
* Wrap any element with this component and you'll get a confirmation
* dialog popup when the element inside is clicked.
*
* <ConfirmDialog onConfirm={() => Do something }
* onCancel={() => Do something}
* content={(props: any) => <div>Are you sure?</div>}
* cancelOnClickOutside={true} >
* <button>Click Me!</button>
* </ConfirmDialog>
*
*/
import * as React from 'react';
import { StyleSheet, css } from 'aphrodite';
import { merge } from 'lodash';
const defaultStyles: ConfirmDialogStyle = {
container: {
position: 'fixed',
top: '0',
bottom: '0',
left: '0',
right: '0',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: 'default',
backgroundColor: 'rgba(0, 0, 0, 0.4)',
},
dialog: {
backgroundColor: '#444',
minWidth: '250px',
minHeight: '100px',
display: 'flex',
flexDirection: 'column',
},
content: {
padding: '10px',
flex: '1 1 auto',
},
buttons: {
display: 'flex',
flex: '0 0 auto',
justifyContent: 'space-around',
alignItems: 'center',
width: '100%',
padding: '10px 20px',
},
confirmButton: {
padding: '5px 10px',
cursor: 'pointer',
':hover': {
backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
},
cancelButton: {
padding: '5px 10px',
cursor: 'pointer',
':hover': {
backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
}
};
export interface ConfirmDialogStyle {
container: React.CSSProperties;
dialog: React.CSSProperties;
content: React.CSSProperties;
buttons: React.CSSProperties;
confirmButton: React.CSSProperties;
cancelButton: React.CSSProperties;
}
export interface ConfirmDialogProps<ContentProps> {
onConfirm: () => void;
onCancel: () => void;
content: (props: ContentProps) => JSX.Element;
cancelOnClickOutside?: boolean;
contentProps?: ContentProps;
style?: Partial<ConfirmDialogStyle>;
confirmButtonContent?: JSX.Element | string;
cancelButtonContent?: JSX.Element | string;
}
export interface ConfirmDialogState {
hidden: boolean;
cancelOnClickOutside: boolean;
}
class ConfirmDialog<ContentProps> extends React.Component<ConfirmDialogProps<ContentProps>, ConfirmDialogState> {
constructor(props: ConfirmDialogProps<ContentProps>) {
super(props);
this.state = {
hidden: true,
cancelOnClickOutside: this.props.cancelOnClickOutside || false,
};
}
componentWillUnmount() {
window.removeEventListener('mousedown', this.windowMouseDown);
}
show = () => {
this.setState({
hidden: false
} as any);
this.mouseOver = false;
}
hide = () => {
this.setState({
hidden: true
} as any);
window.removeEventListener('mousedown', this.windowMouseDown);
this.mouseOver = false;
}
confirm = () => {
this.props.onConfirm();
this.hide();
}
cancel = () => {
this.props.onCancel();
this.hide();
}
mouseOver = false;
onMouseEnter = () => {
this.mouseOver = true;
}
onMouseleave = () => {
this.mouseOver = false;
}
windowMouseDown = () => {
if (this.state.cancelOnClickOutside && !this.state.hidden && !this.mouseOver) {
this.cancel();
}
}
clicked = () => {
if (!this.state.hidden) return;
this.show();
window.addEventListener('mousedown', this.windowMouseDown);
}
render() {
const ss = StyleSheet.create(merge(defaultStyles, this.props.style || {}));
return (
<div onClick={this.clicked} style={{ display: 'inline-block' }}>
{this.props.children}
{
this.state.hidden ? null :
<div className={css(ss.container)}>
<div className={css(ss.dialog)} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseleave}>
<div className={css(ss.content)}>
<this.props.content {...this.props.contentProps} />
</div>
<div className={css(ss.buttons)}>
<div className={css(ss.confirmButton)} onClick={this.confirm}>
{this.props.confirmButtonContent || 'Confirm'}
</div>
<div className={css(ss.cancelButton)} onClick={this.cancel}>
{this.props.cancelButtonContent || 'Cancel'}
</div>
</div>
</div>
</div>
}
</div>
);
}
}
export default ConfirmDialog;
@codecorsair
Copy link
Author

update 1 - Stronger typing and type clarification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment