Browse Source

U SidePanelButton

Piotr Labudda 5 năm trước cách đây
mục cha
commit
3011392a09

+ 6 - 0
SE/se-lib/UI.php

@@ -334,6 +334,12 @@ class UI {
 		UI::endTag('script', "\n");
 	}
 
+	public static function hStyle($cssFile) {
+		$ret = '<style type="text/css">' . "\n";
+		$ret .= file_get_contents($cssFile);
+		$ret .= '</style>' . "\n";
+		return $ret;
+	}
 	public static function inlineCSS($cssFile) {
 		UI::startTag('style', ['type'=>"text/css"], "\n");
 		echo file_get_contents($cssFile);

+ 52 - 8
SE/se-lib/UI/SidePanelButton.php

@@ -3,6 +3,9 @@
 // UI::hAttributes($params);
 // UI::hChildrens($childrens);
 
+// - [x] mode = 'static'
+// - [ ] mode = 'dynamic'
+
 class UI_SidePanelButton implements UITagInterface {
 
 	/**
@@ -13,22 +16,63 @@ class UI_SidePanelButton implements UITagInterface {
 	 * @return string html code
 	 */
 	static function h($tagName, $props = [], $childrens = []) {
-		$jsFuncName = "p5_openSideBar"; // TODO: generate name based on $props['name']
+		$jsFuncName = "p5UI__openSideBar"; // TODO: generate name based on $props['name']
 		if (empty($props['label'])) throw new Exception("Missing 'label'");
+		$mode = V::get('mode', 'static', $props);
+		$name = (!empty($props['name'])) ? $props['name'] : self::generateUniqueName();
+		$idHtmlNode = "p5__js-p5-side_panel-{$name}";
+
+		switch ($mode) {
+			case 'static': {
+				if (empty($props['title'])) throw new Exception("Missing 'title'");
+				if (empty($childrens)) throw new Exception("Missing childrens");
+			} break;
+			case 'dynamic': {
+				throw new Exception("TODO: mode '{$mode}'");
+			} break;
+			default: throw new Exception("Not implemented mode '{$mode}'");
+		}
 
-		return UI::h('span', [], [
-			UI::h('button', [
+		$btnProps = [];
+		if (!empty($props['style'])) $btnProps['style'] = $props['style'];
+		if (!empty($props['class'])) $btnProps['class'] = $props['class'];
+		if (!empty($props['id'])) $btnProps['id'] = $props['id'];
+		// TODO: more attributes, custom, etc.
+
+		return UI::h(null, [], [
+			UI::h('button', array_merge($btnProps, [
 				'onClick' => "return {$jsFuncName}(event, this);",
-				'data-name' => $props['name'],
+				'data-name' => $name,
 				// 'data-url' => $props['url'], // TODO: if dynamic content
-			], $props['label']),
-			UI::h('div', [ 'style' => "display:none" ], [
-
-			]),
+			]), $props['label']),
+			UI::hStyle($cssFile = __FILE__ . ".style.css"),
+			('static' === $mode)
+			?
+				UI::h('div', [ 'style' => "display:none" ], [
+					UI::h('div', [ 'class' => "p5-side_panel p5-side_panel--from-right", 'id' => $idHtmlNode ], [
+						UI::h('header', [ 'class' => "p5-side_panel__header" ], [
+							UI::h('h1', [], $props['title']),
+							UI::h('button', [ 'class' => "p5-side_panel__close p5-side_panel--js-close" ], "Close"),
+						]),
+						UI::h('div', [ 'class' => "p5-side_panel__container" ], [
+							UI::h('div', [ 'class' => "p5-side_panel__content" ], $childrens),
+						]),
+					]),
+				])
+			:	null // created by JS on demand
+			,
 			UI::hScript($jsFile = __FILE__ . '.script.js', $jsonVars = [
 				// 'FUNCTION_NAME' => $jsFuncName,
+				// 'ID_HTML_NODE' => $idHtmlNode,
 			]),
 		]);
 	}
 
+	static function generateUniqueName() {
+		static $_counter = 0;
+
+		$_counter += 1;
+		return "p5_side_panel_{$_counter}";
+	}
+
 }

+ 98 - 0
SE/se-lib/UI/SidePanelButton.php.script.js

@@ -0,0 +1,98 @@
+var DBG = 0;
+var DBG1 = 1;
+
+
+function p5UI__openSideBar(event, targetNode) {
+	event.stopPropagation()
+	event.preventDefault()
+
+	if (!targetNode) targetNode = event.target;
+	var name = targetNode.getAttribute('data-name');
+	if (!name) throw "Missing 'name' in sidebar panel button";
+
+	var idHtmlNode = 'p5__js-p5-side_panel-' + name;
+
+	var panelNode = document.getElementById(idHtmlNode)
+	if (!panelNode) throw "Missing content node";
+
+	// if (panelNode.parentNode !== document.body)
+
+	DBG1 && console.log("DBG:p5UI__openSideBar", {
+		targetNode,
+		name,
+		parent: panelNode.parentNode,
+		parentDiffBody: (panelNode.parentNode !== document.body),
+	});
+
+	if (panelNode.parentNode !== document.body) { // mv at the end of body if not
+		document.body.appendChild(panelNode)
+	}
+	if (!panelNode._p5_onClick) panelNode._p5_onClick = function (event) {
+		// DBG1 && console.log("DBG:_p5_onClick", { self: this })
+		if (hasClass(event.target, 'p5-side_panel--js-close') || hasId(event.target, idHtmlNode)) {
+			event.preventDefault();
+			// removeClass(panelNode, 'p5-side_panel--is-visible');
+			_closeSidePanel(panelNode); // TODO: check use this, not panelNode
+		}
+	}
+	if (!panelNode._p5_onEsc) panelNode._p5_onEsc = function (event) {
+		// DBG1 && console.log("DBG:_p5_onEsc", { self: this })
+		if (event.keyCode == 27) {
+			_closeSidePanel(panelNode); // TODO: check use this, not panelNode
+		}
+	}
+
+	// p5-side_panel p5-side_panel--from-right js-p5-side_panel-main p5-side_panel--is-visible
+	addClass(panelNode, 'p5-side_panel--from-right'); // TODO: from props: left | right
+	if (hasClass(panelNode, 'p5-side_panel--is-visible')) {
+		// removeClass(panelNode, 'p5-side_panel--is-visible');
+		_closeSidePanel(panelNode);
+	} else {
+		setTimeout(function () {
+			// addClass(panelNode, 'p5-side_panel--is-visible')
+			_openSidePanel(panelNode)
+		}, 10)
+	}
+}
+
+function _openSidePanel(panelNode) {
+	addClass(panelNode, 'p5-side_panel--is-visible');
+	document.addEventListener('keyup', panelNode._p5_onEsc);
+	panelNode.addEventListener('click', panelNode._p5_onClick);
+	// fix content scrollTop
+	var contentNode = panelNode.getElementsByClassName('p5-side_panel__content')
+	if (contentNode && contentNode[0]) {
+		contentNode[0].scrollTop = 0;
+	}
+}
+function _closeSidePanel(panelNode) {
+	removeClass(panelNode, 'p5-side_panel--is-visible');
+	document.removeEventListener('keyup', panelNode._p5_onEsc)
+	panelNode.removeEventListener('click', panelNode._p5_onClick);
+}
+
+function hasId(el, id) {
+	return (id === el.getAttribute('id'));
+}
+//class manipulations - needed if classList is not supported
+//https://jaketrent.com/post/addremove-classes-raw-javascript/
+function hasClass(el, className) {
+	if (el.classList) return el.classList.contains(className);
+	else return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)'));
+}
+
+function addClass(el, className) {
+	if (el.classList) el.classList.add(className);
+	else if (!hasClass(el, className)) el.className += " " + className;
+}
+
+function removeClass(el, className) {
+	if (el.classList) el.classList.remove(className);
+	else if (hasClass(el, className)) {
+		var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
+		el.className = el.className.replace(reg, ' ');
+	}
+}
+
+
+module.exports['p5UI__openSideBar'] = p5UI__openSideBar;

+ 69 - 0
SE/se-lib/UI/SidePanelButton.php.style-anim-close-btn.css

@@ -0,0 +1,69 @@
+/* Animated close btn: p5-side_panel__close */
+
+.p5-side_panel__close:hover::before, .p5-side_panel__close:hover::after {
+	-webkit-transition: -webkit-transform 0.3s;
+	transition: -webkit-transform 0.3s;
+	transition: transform 0.3s;
+	transition: transform 0.3s, -webkit-transform 0.3s;
+}
+.p5-side_panel__close:hover::before {
+	-webkit-transform: rotate(220deg);
+		-ms-transform: rotate(220deg);
+			transform: rotate(220deg);
+}
+.p5-side_panel__close:hover::after {
+	-webkit-transform: rotate(135deg);
+		-ms-transform: rotate(135deg);
+			transform: rotate(135deg);
+}
+.p5-side_panel--is-visible .p5-side_panel__close::before {
+	-webkit-animation: cd-close-1 0.6s 0.3s;
+			animation: cd-close-1 0.6s 0.3s;
+}
+.p5-side_panel--is-visible .p5-side_panel__close::after {
+	-webkit-animation: cd-close-2 0.6s 0.3s;
+			animation: cd-close-2 0.6s 0.3s;
+}
+@-webkit-keyframes cd-close-1 {
+	0%, 50% {
+		-webkit-transform: rotate(0deg);
+				transform: rotate(0deg);
+	}
+	100% {
+		-webkit-transform: rotate(45deg);
+				transform: rotate(45deg);
+	}
+}
+
+@keyframes cd-close-1 {
+	0%, 50% {
+		-webkit-transform: rotate(0deg);
+				transform: rotate(0deg);
+	}
+	100% {
+		-webkit-transform: rotate(45deg);
+				transform: rotate(45deg);
+	}
+}
+
+@-webkit-keyframes cd-close-2 {
+	0%, 50% {
+		-webkit-transform: rotate(0deg);
+				transform: rotate(0deg);
+	}
+	100% {
+		-webkit-transform: rotate(-45deg);
+				transform: rotate(-45deg);
+	}
+}
+
+@keyframes cd-close-2 {
+	0%, 50% {
+		-webkit-transform: rotate(0deg);
+				transform: rotate(0deg);
+	}
+	100% {
+		-webkit-transform: rotate(-45deg);
+				transform: rotate(-45deg);
+	}
+}

+ 238 - 0
SE/se-lib/UI/SidePanelButton.php.style.css

@@ -0,0 +1,238 @@
+/* fix h1 baseline (from reset.css) */
+.p5-side_panel__header h1 {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	font-size: 100%;
+	font: inherit;
+	vertical-align: baseline;
+}
+
+/* fix panel__close if button */
+.p5-side_panel__close {
+	border: 0;
+	padding: 0;
+	background: #0000;
+	cursor: pointer;
+	outline: none;
+}
+
+/* -------------------------------- 
+Slide In Panel - by CodyHouse.co
+-------------------------------- */
+
+.p5-side_panel {
+	position: fixed;
+	top: 0;
+	left: 0;
+	height: 100%;
+	width: 100%;
+	visibility: hidden;
+	-webkit-transition: visibility 0s 0.6s;
+	transition: visibility 0s 0.6s;
+}
+
+.p5-side_panel::after {
+	/* overlay layer */
+	content: '';
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	background: transparent;
+	cursor: pointer;
+	-webkit-transition: background 0.3s 0.3s;
+	transition: background 0.3s 0.3s;
+}
+
+.p5-side_panel.p5-side_panel--is-visible {
+	visibility: visible;
+	-webkit-transition: visibility 0s 0s;
+	transition: visibility 0s 0s;
+}
+
+.p5-side_panel.p5-side_panel--is-visible::after {
+	background: rgba(0, 0, 0, 0.6);
+	-webkit-transition: background 0.3s 0s;
+	transition: background 0.3s 0s;
+}
+
+.p5-side_panel__header {
+	position: fixed;
+	width: 90%;
+	height: 50px;
+	line-height: 50px;
+	/* background: rgba(255, 255, 255, 0.96); */
+	background-color: #f5f5f5;
+	background-color: #f5f5f5f5;
+	background-color: #eeeeee;
+	background-color: #eeeeeef5;
+	/* border-bottom: 1px solid #ddd; */
+	z-index: 2;
+	-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.08);
+	box-shadow: 0 1px 1px rgba(0, 0, 0, 0.08);
+	-webkit-transition: -webkit-transform 0.3s 0s;
+	transition: -webkit-transform 0.3s 0s;
+	transition: transform 0.3s 0s;
+	transition: transform 0.3s 0s, -webkit-transform 0.3s 0s;
+	-webkit-transform: translateY(-50px);
+	-ms-transform: translateY(-50px);
+	transform: translateY(-50px);
+}
+
+.p5-side_panel__header h1 {
+	color: #000;
+	font-weight: bold;
+	font-size: 16px;
+	padding-left: 5%;
+}
+
+.p5-side_panel--from-right .p5-side_panel__header {
+	right: 0;
+}
+
+.p5-side_panel--from-left .p5-side_panel__header {
+	left: 0;
+}
+
+.p5-side_panel--is-visible .p5-side_panel__header {
+	-webkit-transition: -webkit-transform 0.3s 0.3s;
+	transition: -webkit-transform 0.3s 0.3s;
+	transition: transform 0.3s 0.3s;
+	transition: transform 0.3s 0.3s, -webkit-transform 0.3s 0.3s;
+	-webkit-transform: translateY(0px);
+	-ms-transform: translateY(0px);
+	transform: translateY(0px);
+}
+
+@media only screen and (min-width: 768px) {
+	.p5-side_panel__header {
+		width: 70%;
+	}
+}
+
+@media only screen and (min-width: 1170px) {
+	.p5-side_panel__header {
+		width: 50%;
+	}
+}
+
+.p5-side_panel__close {
+	position: absolute;
+	top: 0;
+	right: 0;
+	height: 100%;
+	width: 60px;
+	/* image replacement */
+	display: inline-block;
+	overflow: hidden;
+	text-indent: 100%;
+	white-space: nowrap;
+}
+
+.p5-side_panel__close::before,
+.p5-side_panel__close::after {
+	/* close icon created in CSS */
+	content: '';
+	position: absolute;
+	top: 22px;
+	left: 20px;
+	height: 3px;
+	width: 20px;
+	background-color: #424f5c;
+	/* this fixes a bug where pseudo elements are slighty off position */
+	-webkit-backface-visibility: hidden;
+	backface-visibility: hidden;
+}
+
+.p5-side_panel__close::before {
+	-webkit-transform: rotate(45deg);
+	-ms-transform: rotate(45deg);
+	transform: rotate(45deg);
+}
+
+.p5-side_panel__close::after {
+	-webkit-transform: rotate(-45deg);
+	-ms-transform: rotate(-45deg);
+	transform: rotate(-45deg);
+}
+
+.p5-side_panel__close:hover {
+	background-color: #424f5c;
+}
+
+.p5-side_panel__close:hover::before,
+.p5-side_panel__close:hover::after {
+	background-color: #fff;
+}
+
+.p5-side_panel__container {
+	position: fixed;
+	width: 90%;
+	height: 100%;
+	top: 0;
+	background: #fff;
+	z-index: 1;
+	-webkit-transition: -webkit-transform 0.3s 0.3s;
+	transition: -webkit-transform 0.3s 0.3s;
+	transition: transform 0.3s 0.3s;
+	transition: transform 0.3s 0.3s, -webkit-transform 0.3s 0.3s;
+}
+
+.p5-side_panel--from-right .p5-side_panel__container {
+	right: 0;
+	-webkit-transform: translate3d(100%, 0, 0);
+	transform: translate3d(100%, 0, 0);
+}
+
+.p5-side_panel--from-left .p5-side_panel__container {
+	left: 0;
+	-webkit-transform: translate3d(-100%, 0, 0);
+	transform: translate3d(-100%, 0, 0);
+}
+
+.p5-side_panel--is-visible .p5-side_panel__container {
+	-webkit-transform: translate3d(0, 0, 0);
+	transform: translate3d(0, 0, 0);
+	-webkit-transition-delay: 0s;
+	transition-delay: 0s;
+}
+
+@media only screen and (min-width: 768px) {
+	.p5-side_panel__container {
+		width: 70%;
+	}
+}
+
+@media only screen and (min-width: 1170px) {
+	.p5-side_panel__container {
+		width: 50%;
+	}
+}
+
+.p5-side_panel__content {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	padding: 70px 5%;
+	overflow: auto;
+	/* smooth scrolling on touch devices */
+	-webkit-overflow-scrolling: touch;
+}
+
+.p5-side_panel__content p {
+	/* font-size: 1.4rem; */
+	color: #424f5c;
+	line-height: 1.4;
+	margin: 0 0 2em 0;
+}
+
+/* @media only screen and (min-width: 768px) {
+	.p5-side_panel__content p {
+		font-size: 1.6rem;
+		line-height: 1.6;
+	}
+} */