From 9dcd00d9b905d916fbf95cb6172df93ad70022a2 Mon Sep 17 00:00:00 2001 From: Manuel Mtz-Almeida Date: Sat, 16 Sep 2017 10:29:47 -0500 Subject: [PATCH] feat(split-pane): adds split-pane (part 1) --- package-lock.json | 42 +-- .../core/src/components/menu/tests/basic.html | 88 ++---- .../components/split-pane/split-pane.ios.scss | 28 ++ .../components/split-pane/split-pane.md.scss | 28 ++ .../src/components/split-pane/split-pane.scss | 106 +++++++ .../src/components/split-pane/split-pane.tsx | 276 ++++++++++++++++++ .../components/split-pane/split-pane.wp.scss | 28 ++ .../src/components/split-pane/test/basic.html | 79 +++++ 8 files changed, 587 insertions(+), 88 deletions(-) create mode 100644 packages/core/src/components/split-pane/split-pane.ios.scss create mode 100644 packages/core/src/components/split-pane/split-pane.md.scss create mode 100644 packages/core/src/components/split-pane/split-pane.scss create mode 100644 packages/core/src/components/split-pane/split-pane.tsx create mode 100644 packages/core/src/components/split-pane/split-pane.wp.scss create mode 100644 packages/core/src/components/split-pane/test/basic.html diff --git a/package-lock.json b/package-lock.json index 9eec861d70..bf55901e8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,16 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "JSONStream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "dev": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, "abab": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.3.tgz", @@ -1107,8 +1117,8 @@ "integrity": "sha1-4ye1MZThp61dxjR57pCZpSsCSGU=", "dev": true, "requires": { - "is-text-path": "1.0.1", "JSONStream": "1.3.1", + "is-text-path": "1.0.1", "lodash": "4.17.4", "meow": "3.7.0", "split2": "2.1.1", @@ -1137,8 +1147,8 @@ "integrity": "sha512-8od6g684Fhi5Vpp4ABRv/RBsW1AY6wSHbJHEK6FGTv+8jvAAnlABniZu/FVmX9TcirkHepaEsa1QGkRvbg0CKw==", "dev": true, "requires": { - "is-text-path": "1.0.1", "JSONStream": "1.3.1", + "is-text-path": "1.0.1", "lodash": "4.17.4", "meow": "3.7.0", "split2": "2.1.1", @@ -2881,16 +2891,6 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, - "JSONStream": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", - "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", - "dev": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -4659,15 +4659,6 @@ "any-observable": "0.2.0" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", @@ -4688,6 +4679,15 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", diff --git a/packages/core/src/components/menu/tests/basic.html b/packages/core/src/components/menu/tests/basic.html index ca9bd14981..7271383c83 100644 --- a/packages/core/src/components/menu/tests/basic.html +++ b/packages/core/src/components/menu/tests/basic.html @@ -11,6 +11,7 @@ + Left Menu @@ -18,61 +19,19 @@ - - - - Open Right Menu - - - - Close Menu - - - - Close Menu - - - - Close Menu - - - - Close Menu - - - - Close Menu - - - - Close Menu - - - - Close Menu - - - - Close Menu - - - - Close Menu - - - - Close Menu - - - - Close Menu - - - - Close Menu - - + Open Right Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu @@ -84,35 +43,31 @@ - - - + + Hola - + hola macho - + - Menu Basic Test + Ionic CDN demo - - Open left menu - - - Open right menu - + Open left menu + Open right menu + @@ -130,5 +85,4 @@ } - diff --git a/packages/core/src/components/split-pane/split-pane.ios.scss b/packages/core/src/components/split-pane/split-pane.ios.scss new file mode 100644 index 0000000000..48f1dd3914 --- /dev/null +++ b/packages/core/src/components/split-pane/split-pane.ios.scss @@ -0,0 +1,28 @@ + +@import "../../themes/ionic.globals.ios"; +@import "./split-pane"; + +// Split Pane +// -------------------------------------------------- + +/// @prop - Minimum width of the split-pane's side pane +$split-pane-ios-side-min-width: $split-pane-side-min-width !default; + +/// @prop - Maximum width of the split-pane's side pane +$split-pane-ios-side-max-width: $split-pane-side-max-width !default; + +/// @prop - Border style of the side pane +$split-pane-ios-border: $hairlines-width solid $list-ios-border-color !default; + +.split-pane-ios.split-pane-visible >.split-pane-side { + min-width: $split-pane-ios-side-min-width; + max-width: $split-pane-ios-side-max-width; + + border-right: $split-pane-ios-border; + border-left: 0; +} + +.split-pane-ios.split-pane-visible > .split-pane-side[side=right] { + border-right: 0; + border-left: $split-pane-ios-border; +} diff --git a/packages/core/src/components/split-pane/split-pane.md.scss b/packages/core/src/components/split-pane/split-pane.md.scss new file mode 100644 index 0000000000..9204949703 --- /dev/null +++ b/packages/core/src/components/split-pane/split-pane.md.scss @@ -0,0 +1,28 @@ + +@import "../../themes/ionic.globals.md"; +@import "./split-pane"; + +// Split Pane +// -------------------------------------------------- + +/// @prop - Minimum width of the split-pane's side pane +$split-pane-md-side-min-width: $split-pane-side-min-width !default; + +/// @prop - Maximum width of the split-pane's side pane +$split-pane-md-side-max-width: $split-pane-side-max-width !default; + +/// @prop - Border style of the side pane +$split-pane-md-border: 1px solid $list-md-border-color !default; + +.split-pane-md.split-pane-visible >.split-pane-side { + min-width: $split-pane-md-side-min-width; + max-width: $split-pane-md-side-max-width; + + border-right: $split-pane-md-border; + border-left: 0; +} + +.split-pane-md.split-pane-visible > .split-pane-side[side=right] { + border-right: 0; + border-left: $split-pane-md-border; +} diff --git a/packages/core/src/components/split-pane/split-pane.scss b/packages/core/src/components/split-pane/split-pane.scss new file mode 100644 index 0000000000..23f4c5e7cd --- /dev/null +++ b/packages/core/src/components/split-pane/split-pane.scss @@ -0,0 +1,106 @@ + +@import "../../themes/ionic.globals"; + +// Split Pane +// -------------------------------------------------- + +$split-pane-side-min-width: 270px !default; +$split-pane-side-max-width: 28% !default; + +.split-pane { + @include position(0, 0, 0, 0); + + position: absolute; + + display: flex; + + flex-direction: row; + flex-wrap: nowrap; + + contain: strict; +} + +.split-pane-side:not(ion-menu) { + display: none; +} + +.split-pane-visible >.split-pane-side, +.split-pane-visible >.split-pane-main { + @include position(0, 0, 0, 0); + + // scss-lint:disable ImportantRule + position: relative; + + z-index: 0; + + flex: 1; + + box-shadow: none !important; +} + +.split-pane-visible >.split-pane-side { + flex-shrink: 0; + + order: -1; +} + +.split-pane-visible >.split-pane-main, +.split-pane-visible >ion-nav.split-pane-side, +.split-pane-visible >ion-tabs.split-pane-side, +.split-pane-visible >ion-menu.menu-enabled { + display: block; +} + +.split-pane-visible >ion-split-pane.split-pane-side, +.split-pane-visible >ion-split-pane.split-pane-main { + display: flex; +} + +.split-pane-visible >ion-menu.menu-enabled { + >.menu-inner { + @include position-horizontal(0, 0); + + // scss-lint:disable ImportantRule + width: auto; + + box-shadow: none !important; + transform: none !important; + } + + >.ion-backdrop { + // scss-lint:disable ImportantRule + display: hidden !important; + } +} + +.split-pane-visible >.split-pane-side[side=start] { + @include multi-dir() { + order: -1; + } +} + +.split-pane-visible >.split-pane-side[side=end] { + @include multi-dir() { + order: 1; + } +} + +.split-pane-visible >.split-pane-side[side=left] { + @include ltr() { + order: -1; + } + + @include rtl() { + order: 1; + } +} + +.split-pane-visible >.split-pane-side[side=right] { + @include ltr() { + order: 1; + } + + @include rtl() { + order: -1; + } +} diff --git a/packages/core/src/components/split-pane/split-pane.tsx b/packages/core/src/components/split-pane/split-pane.tsx new file mode 100644 index 0000000000..49d64307a4 --- /dev/null +++ b/packages/core/src/components/split-pane/split-pane.tsx @@ -0,0 +1,276 @@ +import { Component, State, Element, Event, EventEmitter, Prop, Method, PropDidChange } from '@stencil/core'; +// import { assert } from '../../utils/helpers'; + +const SPLIT_PANE_MAIN = 'split-pane-main'; +const SPLIT_PANE_SIDE = 'split-pane-side'; +const QUERY: { [key: string]: string } = { + 'xs': '(min-width: 0px)', + 'sm': '(min-width: 576px)', + 'md': '(min-width: 768px)', + 'lg': '(min-width: 992px)', + 'xl': '(min-width: 1200px)', + 'never': '' +}; + + +/** + * @name SplitPane + * + * @description + * SplitPane is a component that makes it possible to create multi-view layout. + * Similar to iPad apps, SplitPane allows UI elements, like Menus, to be + * displayed as the viewport increases. + * + * If the devices screen size is below a certain size, the SplitPane will + * collapse and the menu will become hidden again. This is especially useful when + * creating an app that will be served over a browser or deployed through the app + * store to phones and tablets. + * + * @usage + * To use SplitPane, simply add the component around your root component. + * In this example, we'll be using a sidemenu layout, similar to what is + * provided from the sidemenu starter template. + * + * ```html + * + * + * + * + * + * Menu + * + * + * + * + * + * + * + * ``` + * + * Here, SplitPane will look for the element with the `main` attribute and make + * that the central component on larger screens. The `main` component can be any + * Ionic component (`ion-nav` or `ion-tabs`) except `ion-menu`. + * + * ### Setting breakpoints + * + * By default, SplitPane will expand when the screen is larger than 768px. + * If you want to customize this, use the `when` input. The `when` input can + * accept any valid media query, as it uses `matchMedia()` underneath. + * + * ``` + * + * + * + * + * .... + * + * + * + * + * + * ``` + * + * SplitPane also provides some predefined media queries that can be used. + * + * ```html + * + * + * ... + * + * ``` + * + * + * | Size | Value | Description | + * |------|-----------------------|-----------------------------------------------------------------------| + * | `xs` | `(min-width: 0px)` | Show the split-pane when the min-width is 0px (meaning, always) | + * | `sm` | `(min-width: 576px)` | Show the split-pane when the min-width is 576px | + * | `md` | `(min-width: 768px)` | Show the split-pane when the min-width is 768px (default break point) | + * | `lg` | `(min-width: 992px)` | Show the split-pane when the min-width is 992px | + * | `xl` | `(min-width: 1200px)` | Show the split-pane when the min-width is 1200px | + * + * You can also pass in boolean values that will trigger SplitPane when the value + * or expression evaluates to true. + * + * + * ```html + * + * ... + * + * ``` + * + * ```ts + * class MyClass { + * public isLarge = false; + * constructor(){} + * } + * ``` + * + * Or + * + * ```html + * + * ... + * + * ``` + * + * ```ts + * class MyClass { + * constructor(){} + * shouldShow(){ + * if(conditionA){ + * return true + * } else { + * return false + * } + * } + * } + * ``` + * + */ +@Component({ + tag: 'ion-split-pane', + styleUrls: { + ios: 'split-pane.ios.scss', + md: 'split-pane.md.scss', + wp: 'split-pane.wp.scss' + }, + host: { + theme: 'split-pane' + } +}) +export class SplitPane { + + private rmL: any; + @Element() private ele: HTMLElement; + @State() private visible: boolean = false; + + /** + * @input {boolean} If `false`, the split-pane is disabled, ie. the side pane will + * never be displayed. Default `true`. + */ + @Prop() enabled: boolean = true; + + /** + * @input {string | boolean} When the split-pane should be shown. + * Can be a CSS media query expression, or a shortcut expression. + * Can also be a boolean expression. + */ + @Prop() when: string | boolean = QUERY['md']; + + /** + * @output {any} Expression to be called when the split-pane visibility has changed + */ + @Event() ionChange: EventEmitter; + + ionViewDidLoad() { + this._styleChildren(); + this._updateQuery(); + } + + ionViewDidUnload() { + this.rmL && this.rmL(); + this.rmL = null; + } + + private _styleChildren() { + const children = this.ele.children; + const nu = this.ele.childElementCount; + let foundMain = false; + for (var i = 0; i < nu; i++) { + var child = children[i] as HTMLElement; + var isMain = child.hasAttribute('main'); + if (isMain) { + if (foundMain) { + // console.warn('split pane can not have more than one main node'); + return; + } + foundMain = true; + } + setPaneClass(child, isMain); + } + // if (!foundMain) { + // console.warn('split pane could not found any main node'); + // } + } + + @PropDidChange('when') + _updateQuery() { + this.rmL && this.rmL(); + this.rmL = null; + + // Check if the split-pane is disabled + if (!this.enabled) { + this._setVisible(false); + return; + } + + // When query is a boolean + const query = this.when; + if (typeof query === 'boolean') { + this._setVisible(query); + return; + } + + // When query is a string, let's find first if it is a shortcut + const defaultQuery = QUERY[query]; + const mediaQuery = (defaultQuery) + ? defaultQuery + : query; + + // Media query is empty or null, we hide it + if (!mediaQuery || mediaQuery.length === 0) { + this._setVisible(false); + return; + } + + // Listen on media query + const callback = (q: MediaQueryList) => this._setVisible(q.matches); + const mediaList = window.matchMedia(mediaQuery); + mediaList.addListener(callback); + this.rmL = () => mediaList.removeListener(callback); + this._setVisible(mediaList.matches); + } + + private _setVisible(visible: boolean) { + if (this.visible !== visible) { + this.visible = visible; + this.ionChange.emit(this); + } + } + + /** + * @hidden + */ + @Method() + isVisible(): boolean { + return this.visible; + } + + hostData() { + return { + class: { + 'split-pane-visible': this.visible + } + }; + } + + render() { + return ; + } + +} + +function setPaneClass(ele: HTMLElement, isMain: boolean) { + let toAdd; + let toRemove; + if (isMain) { + toAdd = SPLIT_PANE_MAIN; + toRemove = SPLIT_PANE_SIDE; + } else { + toAdd = SPLIT_PANE_SIDE; + toRemove = SPLIT_PANE_MAIN; + } + const classList = ele.classList; + classList.add(toAdd); + classList.remove(toRemove); +} diff --git a/packages/core/src/components/split-pane/split-pane.wp.scss b/packages/core/src/components/split-pane/split-pane.wp.scss new file mode 100644 index 0000000000..fbcc3993d0 --- /dev/null +++ b/packages/core/src/components/split-pane/split-pane.wp.scss @@ -0,0 +1,28 @@ + +@import "../../themes/ionic.globals.wp"; +@import "./split-pane"; + +// Split Pane +// -------------------------------------------------- + +/// @prop - Minimum width of the split-pane's side pane +$split-pane-wp-side-min-width: $split-pane-side-min-width !default; + +/// @prop - Maximum width of the split-pane's side pane +$split-pane-wp-side-max-width: $split-pane-side-max-width !default; + +/// @prop - Border style of the side pane +$split-pane-wp-border: 1px solid $list-wp-border-color !default; + +.split-pane-wp.split-pane-visible >.split-pane-side { + min-width: $split-pane-wp-side-min-width; + max-width: $split-pane-wp-side-max-width; + + border-right: $split-pane-wp-border; + border-left: 0; +} + +.split-pane-wp.split-pane-visible > .split-pane-side[side=right] { + border-right: 0; + border-left: $split-pane-wp-border; +} diff --git a/packages/core/src/components/split-pane/test/basic.html b/packages/core/src/components/split-pane/test/basic.html new file mode 100644 index 0000000000..c688760112 --- /dev/null +++ b/packages/core/src/components/split-pane/test/basic.html @@ -0,0 +1,79 @@ + + + + + + Ionic Item Sliding + + + + + + + + + + + + + Left Menu + + + + + + Open Right Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + Close Menu + + + + + + Footer + + + + + + + + + + Ionic CDN demo + + + + + Open left menu + Open right menu + + + + + + + + + + +