#app
View Compiled
// ----- Variable Declarations -----

// ----- .zxc-confirm-btn
$zxc-confirm-btn-background-color: #c4c4c4;
$zxc-confirm-btn-color: #151515;

// ----- .zxc-confirm-btn-primary
$zxc-confirm-btn-primary-background-color: #0795eb;
$zxc-confirm-btn-primary-color: #ffffff;

// ----- .zxc-confirm-overlay
$zxc-confirm-overlay-background-color: rgba(0, 0, 0, 0.7);

// ----- .zxc-confirm-message
$zxc-confirm-message-background-color: #ffffff;
$zxc-confirm-message-padding: 1.2rem;

// ----- .zxc-confirm-buttons
$zxc-confirm-buttons-background-color: #ffffff;
$zxc-confirm-buttons-border-top: solid 1px #e4e4e4;
$zxc-confirm-buttons-padding: 0.7rem;

// ----- .zxc-confirm-dialog
$zxc-confirm-dialog-background-color: #ffffff;

// ----- Mixins -----

@mixin button($color, $background-color) {
	color: $color;
	font-family: inherit;
	font-weight: bold;
	text-align: center;
	background-color: $background-color;
	padding: 0.8rem 1.5rem;
	margin: 0;
	border: none;
	outline: none;
	transition: background-color 150ms ease-in-out, box-shadow 300ms ease-in-out;
	&:not(:first-child) {
		margin-left: 0.7rem;
	}
	&:hover {
		background-color: lighten($background-color, 10%);
	}
	&:active {
		background-color: darken($background-color, 10%);
	}
	&:focus {
		box-shadow: 0 0 0 0.3rem rgba($background-color, 0.5);
	}
}

// ----- CSS Styles -----

.zxc-confirm-button-text {
	@include button(
		$zxc-confirm-btn-primary-color,
		$zxc-confirm-btn-primary-background-color);
	width: 100%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	user-select: none;
	cursor: default;
}

.zxc-confirm-overlay {
	font-family: inherit;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	position: relative;
	transition: background-color 500ms ease-in-out;
	&.zxc-confirm-active {
		background-color: $zxc-confirm-overlay-background-color;
		width: 100vw;
		height: 100vh;
		position: fixed;
		top: 0;
		left: 0;
		z-index: 1;
		transition: background-color 500ms ease-in-out 1000ms;
	}
}

.zxc-confirm-dialog {
	font-family: inherit;
	background-color: $zxc-confirm-dialog-background-color;
	position: relative;
	top: 0;
	left: 0;
}

.zxc-confirm-content {
	height: 0;
	overflow: hidden;
}

.zxc-confirm-message {
	font-family: inherit;
	background-color: $zxc-confirm-message-background-color;
	padding: $zxc-confirm-message-padding;
	box-sizing: border-box;
}

.zxc-confirm-buttons {
	text-align: right;
	background-color: $zxc-confirm-buttons-background-color;
	padding: $zxc-confirm-buttons-padding;
	border-top: $zxc-confirm-buttons-border-top;
	box-sizing: border-box;
}

.zxc-confirm-btn {
	@include button(
		$zxc-confirm-btn-color,
		$zxc-confirm-btn-background-color);
}

.zxc-confirm-btn-primary {
	@include button(
		$zxc-confirm-btn-primary-color,
		$zxc-confirm-btn-primary-background-color);
}

// ----- Extras -----

@import url("https://fonts.googleapis.com/css?family=Raleway:400,400i,700");

#app {
	font-family: Raleway, sans-serif;
	padding: 20px;
}
View Compiled
// ----- Component

class ZxcConfirm extends React.Component {
	/**
	 * Constructor for the component. Basically, all of the global variables (that must be a read-only) are set here
	 */
	constructor(props) {
		super(props);
		
		// ----- Read-only variables
		this.languageVariable = {
			en: {
				btnText: 'Show confirm dialog',
				confirmMessage: 'Do you want to perform this action?',
				confirm: 'Confirm',
				cancel: 'Cancel'
			}, ja: {
				btnText: '確認ダイアログを表示',
				confirmMessage: '処理を行いますが、よろしいですか?',
				confirm: '確認',
				cancel: '取消'
			}
		}
		
		// ----- Events
		this.showConfirmMsg = this.showConfirmMsg.bind(this);
		this.hideConfirmMsg = this.hideConfirmMsg.bind(this);
		this.confirmClicked = this.confirmClicked.bind(this);
	}
	
	/**
	 * When the React component is mounted, all of the variables that are rewritable are here
	 */
	componentDidMount() {
		// ----- Variables
		this.confirmClass = 'zxc-confirm-overlay';
		this.pos = {
			x: 0,
			y: 0
		};
		this.orig = {
			width: 0
		};
		
		// ----- States
		this.setState({confirmIsActive: false});
	}
	
	/**
	 * Text or label will be displayed depending on selected language
	 */
	lang(_lang, text) {
		_lang = (typeof _lang == 'undefined') ? 'en' : _lang;
		
		return this.languageVariable[_lang][text];
	}
	
	/**
	 * Click event for Confirm button: If clicked, the button will be a confirm dialog
	 */
	showConfirmMsg() {
		if (this.state.confirmIsActive == false) {
			let dialog  = this.refs.dialog;
			let content = this.refs.content;
			let coords  = dialog.getBoundingClientRect();
			
			this.setState({confirmIsActive: true});
			
			this.confirmClass = 'zxc-confirm-overlay zxc-confirm-active';
			
			this.pos.x = coords.left;
			this.pos.y = coords.top;
			
			this.orig.width = dialog.clientWidth;
			
			TweenLite.fromTo(dialog, 0.8, {
				position: 'absolute',
				top: this.pos.y + 'px',
				left: this.pos.x + 'px'
			}, {
				top: ((window.innerHeight / 2) - (dialog.clientHeight / 2)) + 'px',
				left: ((window.innerWidth / 2) - (dialog.clientWidth / 2)) + 'px',
				ease: Elastic.easeOut.config(1.5, 0.4)
			}).eventCallback('onComplete', () => {
				TweenLite.to(dialog, 0, {position: 'relative', top: 0, left: 0});
				TweenLite.to(dialog, 0.8, {
					width: '400px',
					ease: Back.easeOut.config(3)
				}).delay(0.5);
				TweenLite.to(content, 0.8, {
					height: content.scrollHeight + 'px',
					ease: Bounce.easeOut
				}).delay(1);
				TweenLite.to(content, 1, {height: 'auto'}).delay(2);
			});
		}
	}
	
	/**
	 * Click event for Cancel button: If clicked, the dialog will go back to its normal state
	 */
	hideConfirmMsg() {
		if (this.state.confirmIsActive == true) {
			let dialog  = this.refs.dialog;
			let content = this.refs.content;
			let pos     = this.pos;
			let orig    = this.orig;
			
			let overlayTimeout = setTimeout(() => {
				this.confirmClass = 'zxc-confirm-overlay';
				clearTimeout(overlayTimeout);
			}, 2000);
			
			TweenLite.to(content, 0.5, {height: '0px', ease: Back.easeIn.config(2)});
			TweenLite.to(dialog, 0.5, {
				width: orig.width + 'px',
				ease: Bounce.easeOut
			}).delay(0.7).eventCallback('onComplete', () => {
				TweenLite.fromTo(dialog, 0.8, {
					position: 'absolute',
					top: ((window.innerHeight / 2) - (dialog.clientHeight / 2)) + 'px',
					left: ((window.innerWidth / 2) - (dialog.clientWidth / 2)) + 'px'
				}, {
					top: pos.y + 'px',
					left: pos.x + 'px',
					ease: Elastic.easeOut.config(1.5, 0.4)
				}).delay(0.1).eventCallback('onComplete', () => {
					TweenLite.to(dialog, 0, {position: 'relative', top: 0, left: 0});
					
					this.setState({confirmIsActive: false});
				});
			});
		}
	}
	
	/**
	 * Event handler when the 'confirm' button has been clicked
	 */
	confirmClicked() {
		alert('Confirm is clicked, though, the confirm box will still hide');
		
		this.hideConfirmMsg();
	}
	
	/**
	 * Render the text for button text (or title)
	 */
	renderButtonText() {
		return (typeof this.props.text == 'undefined')
			? this.lang(this.props.lang, 'btnText')
			: this.props.text;
	}
	
	/**
	 * Render the text for confirm dialog message
	 */
	renderConfirmMessage() {
		return (typeof this.props.message == 'undefined')
			? this.lang(this.props.lang, 'confirmMessage')
			: this.props.message;
	}
	
	/**
	 * Render the text for action buttons
	 */
	renderButtons(which) {
		return (typeof this.props[which] == 'undefined')
			? this.lang(this.props.lang, which)
			: this.props[which];
	}
	
	/**
	 * Render the JSX component
	 */
	render() {
		return (
			<div className={this.confirmClass}>
				<div className="zxc-confirm-dialog" ref="dialog">
					<div className="zxc-confirm-button-text" onClick={this.showConfirmMsg}>
						{this.renderButtonText()}
					</div>
					<div className="zxc-confirm-content" ref="content">
						<div className="zxc-confirm-message">
							{this.renderConfirmMessage()}
						</div>
						<div className="zxc-confirm-buttons">
							<button className="zxc-confirm-btn" onClick={this.hideConfirmMsg}>
								{this.renderButtons('cancel')}
							</button>
							<button className="zxc-confirm-btn-primary" onClick={this.confirmClicked}>
								{this.renderButtons('confirm')}
							</button>
						</div>
					</div>
				</div>
			</div>
		);
	}
}

// ----- React VM

const lang = 'en';
const text = {
	en: {
		btnText: 'Show confirm dialog'
	}, ja: {
		btnText: '確認ダイアログを表示'
	}
};
const elm = (
	<div>
		<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
		<ZxcConfirm lang={lang} />
		<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
		<ZxcConfirm lang={lang} text="Custom message" message="Messages and buttons' text can be changed" confirm="I understood" cancel="Nah!" />
		<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
		<ZxcConfirm lang="ja" text="他の言語" message="他の言語もサポートできます" confirm="了解" cancel="閉じる" />
		<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
		<ZxcConfirm lang="ja" />
		<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
		<ZxcConfirm text="Default language" message="English is the default language by default" />
	</div>
);

ReactDOM.render(
	elm,
	document.querySelector('#app')
);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js