From db9e9d9159f3faad417e48d1c1d66eea3626fb9c Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Wed, 12 Jul 2017 16:04:28 -0400 Subject: [PATCH] feat(fab): add fab container and buttons --- .../core/src/components/fab/fab-container.tsx | 107 +++++++++ packages/core/src/components/fab/fab-list.tsx | 63 +++++ packages/core/src/components/fab/fab.ios.scss | 75 ++++++ packages/core/src/components/fab/fab.md.scss | 106 +++++++++ packages/core/src/components/fab/fab.scss | 224 ++++++++++++++++++ packages/core/src/components/fab/fab.tsx | 201 ++++++++++++++++ packages/core/src/components/fab/fab.wp.scss | 74 ++++++ .../core/src/components/fab/test/basic.html | 145 ++++++++++++ packages/core/stencil.config.js | 3 +- 9 files changed, 997 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/components/fab/fab-container.tsx create mode 100644 packages/core/src/components/fab/fab-list.tsx create mode 100755 packages/core/src/components/fab/fab.ios.scss create mode 100755 packages/core/src/components/fab/fab.md.scss create mode 100755 packages/core/src/components/fab/fab.scss create mode 100755 packages/core/src/components/fab/fab.tsx create mode 100755 packages/core/src/components/fab/fab.wp.scss create mode 100644 packages/core/src/components/fab/test/basic.html diff --git a/packages/core/src/components/fab/fab-container.tsx b/packages/core/src/components/fab/fab-container.tsx new file mode 100644 index 0000000000..c915da03d2 --- /dev/null +++ b/packages/core/src/components/fab/fab-container.tsx @@ -0,0 +1,107 @@ +import { Component, h, Method } from '@stencil/core'; + + +/** + * @name FabContainer + * @module ionic + * + * @description + * `` is not a FAB button by itself but a container that assist the fab button (` + * + * + * + * + * + * + * + * ``` + * + * Ionic's FAB also supports "material design's fab speed dial". It is a normal fab button + * that shows a list of related actions when clicked. + * + * The same `ion-fab` container can contain several `ion-fab-list` with different side values: + * `top`, `bottom`, `left` and `right`. For example, if you want to have a list of button that are + * on the top of the main button, you should use `side="top"` and so on. By default, if side is ommited, `side="bottom"`. + * + * ```html + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * ``` + * + * A FAB speed dial can also be closed programatically. + * + * ```html + * + * + * + * + * + * + * + * + * + * ``` + * + * ```ts + * share(socialNet: string, fab: FabContainer) { + * fab.close(); + * console.log("Sharing in", socialNet); + * } + * ``` + * + * @demo /docs/demos/src/fab/ + * @see {@link /docs/components#fabs FAB Component Docs} + */ + +@Component({ + tag: 'ion-fab', +}) +export class FabContainer { + $el: HTMLElement; + + /** + * Close an active FAB list container + */ + @Method() + close() { + const fab: any = this.$el.querySelector('ion-fab-button'); + fab.close(); + } + + render() { + return ( + + ); + } + +} \ No newline at end of file diff --git a/packages/core/src/components/fab/fab-list.tsx b/packages/core/src/components/fab/fab-list.tsx new file mode 100644 index 0000000000..aa576ca3f6 --- /dev/null +++ b/packages/core/src/components/fab/fab-list.tsx @@ -0,0 +1,63 @@ +import { Component, h, PropDidChange, State, VNodeData } from '@stencil/core'; + +import { HostElement } from '../../utils/interfaces'; + + +/** + * @name FabList + * @description + * `ion-fab-list` is a container for multiple FAB buttons. They are components of `ion-fab` and allow you to specificy the buttons position, left, right, top, bottom. + * @usage + * + * ```html + * + * + * + * + * + * + * + * + * + * + * + * ``` + * @module ionic + * + * @demo /docs/demos/src/fab/ + * @see {@link /docs/components#fab Fab Component Docs} + */ +@Component({ + tag: 'ion-fab-list', +}) +export class FabList { + $el: HTMLElement; + + @State() activated: boolean = false; + + @PropDidChange('activated') + activatedChange(activated: boolean) { + const fabs = this.$el.querySelectorAll('ion-fab-button') as NodeListOf; + + // if showing the fabs add a timeout, else show immediately + var timeout = activated ? 30 : 0; + for (var i = 0; i < fabs.length; i++) { + const fab = fabs[i].$instance; + setTimeout(() => fab.show = activated, i * timeout); + } + } + + hostData(): VNodeData { + return { + class: { + 'fab-list-active': this.activated + } + }; + } + + render() { + return ( + + ); + } +} diff --git a/packages/core/src/components/fab/fab.ios.scss b/packages/core/src/components/fab/fab.ios.scss new file mode 100755 index 0000000000..f8b1e1e01b --- /dev/null +++ b/packages/core/src/components/fab/fab.ios.scss @@ -0,0 +1,75 @@ +@import "../../themes/ionic.globals.ios"; +@import "./fab"; + +// iOS FAB Button +// -------------------------------------------------- + +/// @prop - Background color of the button +$fab-ios-background-color: color($colors-ios, primary) !default; + +/// @prop - Text color of the button +$fab-ios-text-color: color-contrast($colors-ios, $fab-ios-background-color) !default; + +/// @prop - Background color of the activated button +$fab-ios-background-color-activated: color-shade($fab-ios-background-color) !default; + +/// @prop - Background color of the button in a list +$fab-ios-list-button-background-color: $fab-list-button-background-color !default; + +/// @prop - Text color of the button in a list +$fab-ios-list-button-text-color: color-contrast($colors-ios, $fab-ios-list-button-background-color) !default; + +/// @prop - Background color of the activated button in a list +$fab-ios-list-button-background-color-activated: color-shade($fab-ios-list-button-background-color) !default; + +/// @prop - Transition duration of the transform and opacity of the button in a list +$fab-ios-list-button-transition-duration: 200ms !default; + +/// @prop - Speed curve of the transition of the transform and opacity of the button in a list +$fab-ios-list-button-transition-timing-function: ease !default; + +/// @prop - Transition delay of the transform and opacity of the button in a list +$fab-ios-list-button-transition-delay: 10ms !default; + + +.fab-ios { + color: $fab-ios-text-color; + background-color: $fab-ios-background-color; +} + +.fab-ios.activated { + background-color: $fab-ios-background-color-activated; +} + +.fab-ios-in-list { + color: $fab-ios-list-button-text-color; + background-color: $fab-ios-list-button-background-color; + + transition: transform $fab-ios-list-button-transition-duration $fab-ios-list-button-transition-timing-function $fab-ios-list-button-transition-delay, + opacity $fab-ios-list-button-transition-duration $fab-ios-list-button-transition-timing-function $fab-ios-list-button-transition-delay; +} + +.fab-ios-in-list.activated { + background-color: $fab-ios-list-button-background-color-activated; +} + + +// Generate iOS FAB colors +// -------------------------------------------------- + +@each $color-name, $color-base, $color-contrast in get-colors($colors-ios) { + + $bg-color: $color-base; + $bg-color-activated: color-shade($bg-color); + $fg-color: $color-contrast; + + .fab-ios-#{$color-name} { + color: $fg-color; + background-color: $bg-color; + } + + .fab-ios-#{$color-name}.activated { + background-color: $bg-color-activated; + } +} + diff --git a/packages/core/src/components/fab/fab.md.scss b/packages/core/src/components/fab/fab.md.scss new file mode 100755 index 0000000000..580312a489 --- /dev/null +++ b/packages/core/src/components/fab/fab.md.scss @@ -0,0 +1,106 @@ +@import "../../themes/ionic.globals.md"; +@import "./fab"; + +// Material Design FAB Button +// -------------------------------------------------- + +/// @prop - Box shadow of the FAB button +$fab-md-box-shadow: 0 4px 6px 0 rgba(0, 0, 0, .14), 0 4px 5px rgba(0, 0, 0, .1) !default; + +/// @prop - Box shadow of the activated FAB button +$fab-md-box-shadow-activated: 0 5px 15px 0 rgba(0, 0, 0, .4), 0 4px 7px 0 rgba(0, 0, 0, .1) !default; + +/// @prop - Background color of the button +$fab-md-background-color: color($colors-md, primary) !default; + +/// @prop - Text color of the button +$fab-md-text-color: color-contrast($colors-md, $fab-md-background-color) !default; + +/// @prop - Background color of the activated button +$fab-md-background-color-activated: color-shade($fab-md-background-color) !default; + +/// @prop - Background color of the button in a list +$fab-md-list-button-background-color: $fab-list-button-background-color !default; + +/// @prop - Text color of the button in a list +$fab-md-list-button-text-color: color-contrast($colors-md, $fab-md-list-button-background-color) !default; + +/// @prop - Background color of the activated button in a list +$fab-md-list-button-background-color-activated: color-shade($fab-md-list-button-background-color) !default; + +/// @prop - Transition duration of the transform and opacity of the button in a list +$fab-md-list-button-transition-duration: 200ms !default; + +/// @prop - Speed curve of the transition of the transform and opacity of the button in a list +$fab-md-list-button-transition-timing-function: ease !default; + +/// @prop - Transition delay of the transform and opacity of the button in a list +$fab-md-list-button-transition-delay: 10ms !default; + +$fab-button-md-transition-duration: 300ms !default; + +$fab-button-md-transition-timing-function: cubic-bezier(.4, 0, .2, 1) !default; + + +.fab-md { + color: $fab-md-text-color; + background-color: $fab-md-background-color; + + box-shadow: $fab-md-box-shadow; + + transition: box-shadow $fab-button-md-transition-duration $fab-button-md-transition-timing-function, + background-color $fab-button-md-transition-duration $fab-button-md-transition-timing-function, + color $fab-button-md-transition-duration $fab-button-md-transition-timing-function; +} + +.fab-md.activated { + background-color: $fab-md-background-color-activated; + box-shadow: $fab-md-box-shadow-activated; +} + +.fab-md-in-list { + color: $fab-md-list-button-text-color; + background-color: $fab-md-list-button-background-color; + + transition: transform $fab-md-list-button-transition-duration $fab-md-list-button-transition-timing-function $fab-md-list-button-transition-delay, + opacity $fab-md-list-button-transition-duration $fab-md-list-button-transition-timing-function $fab-md-list-button-transition-delay, + box-shadow $fab-button-md-transition-duration $fab-button-md-transition-timing-function, + background-color $fab-button-md-transition-duration $fab-button-md-transition-timing-function, + color $fab-button-md-transition-duration $fab-button-md-transition-timing-function; +} + +.fab-md-in-list.activated { + background-color: $fab-md-list-button-background-color-activated; +} + +// Material Design FAB Ripple +// -------------------------------------------------- + +.fab-md .button-effect { + background-color: color-contrast($colors-md, $fab-md-background-color); +} + + +// Generate MD FAB colors +// -------------------------------------------------- + +@each $color-name, $color-base, $color-contrast in get-colors($colors-md) { + + $bg-color: $color-base; + $bg-color-activated: color-shade($bg-color); + $fg-color: $color-contrast; + + .fab-md-#{$color-name} { + color: $fg-color; + background-color: $bg-color; + } + + .fab-md-#{$color-name}.activated { + background-color: $bg-color-activated; + } + + .fab-md-#{$color-name} .button-effect { + background-color: $fg-color; + } +} + diff --git a/packages/core/src/components/fab/fab.scss b/packages/core/src/components/fab/fab.scss new file mode 100755 index 0000000000..02ec0931a4 --- /dev/null +++ b/packages/core/src/components/fab/fab.scss @@ -0,0 +1,224 @@ +@import "../../themes/ionic.globals"; + +// Floating Action Buttons +// -------------------------------------------------- + +/// @prop - Width and height of the FAB button +$fab-size: 56px !default; + +/// @prop - Width and height of the FAB button mini +$fab-mini-size: 40px !default; + +/// @prop - Margin of the FAB Container +$fab-content-margin: 10px !default; + +/// @prop - Margin of the FAB List +$fab-list-margin: 10px !default; + +/// @prop - Background color of the button in a list +$fab-list-button-background-color: #f4f4f4 !default; + + +.fab { + @include text-align(center); + @include appearance(none); + @include border-radius(50%); + + position: relative; + z-index: 0; + display: block; + overflow: hidden; + + width: $fab-size; + height: $fab-size; + + font-size: 14px; + line-height: $fab-size; + text-overflow: ellipsis; + text-transform: none; + white-space: nowrap; + cursor: pointer; + transition: background-color, opacity 100ms linear; + + background-clip: padding-box; + font-kerning: none; + user-select: none; + + contain: strict; +} + +.fab ion-icon { + flex: 1; + + font-size: 2.4rem; +} + +// FAB mini +// -------------------------------------------------- + +.fab[mini] { + @include margin(($fab-size - $fab-mini-size) / 2); + + width: $fab-mini-size; + height: $fab-mini-size; + + line-height: $fab-mini-size; +} + +.fab[mini] .fab-close-icon { + line-height: $fab-mini-size; +} + + +// FAB container +// -------------------------------------------------- + +ion-fab { + position: absolute; + z-index: $z-index-fixed-content; + + &[center] { + @include position(null, null, null, 50%); + @include margin-horizontal(-$fab-size / 2, null); + } + + &[middle] { + @include margin(-$fab-size / 2, null, null, null); + + top: 50%; + } + + &[top] { + top: $fab-content-margin; + } + + &[right] { + // scss-lint:disable PropertySpelling + @include multi-dir() { + right: $fab-content-margin; + } + } + + &[end] { + @include position-horizontal(null, $fab-content-margin); + } + + &[bottom] { + bottom: $fab-content-margin; + } + + &[left] { + // scss-lint:disable PropertySpelling + @include multi-dir() { + left: $fab-content-margin; + } + } + + &[start] { + @include position-horizontal($fab-content-margin, null); + } + + &[top][edge] { + top: -$fab-size / 2; + } + + &[bottom][edge] { + bottom: -$fab-size / 2; + } +} + + +// FAB list (speed dial) +// -------------------------------------------------- + +ion-fab-list { + @include margin($fab-size + $fab-list-margin, 0); + + position: absolute; + top: 0; + display: none; + + flex-direction: column; + align-items: center; + + min-width: $fab-size; + min-height: $fab-size; +} + +.fab-in-list { + @include margin(8px, 0); + + width: $fab-mini-size; + height: $fab-mini-size; + + opacity: 0; + visibility: hidden; + transform: scale(0); +} + +.fab-in-list.show { + opacity: 1; + visibility: visible; + transform: scale(1); +} + +ion-fab-list[side=left] .fab-in-list, +ion-fab-list[side=right] .fab-in-list { + @include margin(0, 8px); +} + +ion-fab-list[side=top] { + top: auto; + bottom: 0; + + flex-direction: column-reverse; +} + +ion-fab-list[side=left] { + @include margin(0, $fab-size + $fab-list-margin); + @include position-horizontal(null, 0); + + flex-direction: row-reverse; +} + +ion-fab-list[side=right] { + @include margin(0, $fab-size + $fab-list-margin); + @include position(null, null, null, 0); + + flex-direction: row; +} + + +// FAB animation +// -------------------------------------------------- + +.fab-list-active { + display: flex; +} + +.fab-close-icon { + @include position(0, 0, null, 0); + + position: absolute; + + line-height: $fab-size; + opacity: 0; + transform: scale(.4) rotateZ(-45deg); + transition: all ease-in-out 300ms; + transition-property: transform, opacity; +} + +.fab .button-inner { + transition: all ease-in-out 300ms; + transition-property: transform, opacity; +} + +.fab-close-active .fab-close-icon { + opacity: 1; + transform: scale(1) rotateZ(0deg); +} + +.fab-close-active .button-inner { + opacity: 0; + transform: scale(.4) rotateZ(45deg); +} diff --git a/packages/core/src/components/fab/fab.tsx b/packages/core/src/components/fab/fab.tsx new file mode 100755 index 0000000000..a19a034e9f --- /dev/null +++ b/packages/core/src/components/fab/fab.tsx @@ -0,0 +1,201 @@ +import { Component, CssClassObject, h, Method, Prop, State } from '@stencil/core'; + +import { createThemedClasses } from '../../utils/theme'; +import { HostElement } from '../../utils/interfaces'; + + +/** + * @name FabButton + * @module ionic + * + * @description + * FABs (Floating Action Buttons) are standard material design components. They are shaped as a circle that represents a promoted action. When pressed, it may contain more related actions. + * FABs as its name suggests are floating over the content in a fixed position. This is not achieved exclusively with `` but it has to wrapped with the `` component, like this: + * + * ```html + * + * + * + * + * + * + * + * + * + * + * ``` + * + * In case the button is not wrapped with ``, the fab button will behave like a normal button, scrolling with the content. + * + * See [ion-fab] to learn more information about how to position the fab button. + * + * @property [mini] - Makes a fab button with a reduced size. + * + * @usage + * + * ```html + * + * + * + * + * + * + * + * + * + * + * ``` + * + * @demo /docs/demos/src/fab/ + * @see {@link /docs/components#fabs FAB Component Docs} + */ +@Component({ + tag: 'ion-fab-button', + styleUrls: { + ios: 'fab.ios.scss', + md: 'fab.md.scss', + wp: 'fab.wp.scss' + } +}) +export class FabButton { + $el: HTMLElement; + mode: string; + color: string; + + @State() activated: boolean = false; + + @State() closeActivated: boolean = false; + + @State() show: boolean = false; + + @State() inContainer: boolean = false; + + @State() inList: boolean = false; + + @Prop() href: string; + + /** + * @Prop {boolean} If true, sets the button into a disabled state. + */ + @Prop() disabled: boolean = false; + + ionViewDidLoad() { + const parentNode = this.$el.parentNode.nodeName; + + this.inList = (parentNode === 'ION-FAB-LIST'); + this.inContainer = (parentNode === 'ION-FAB'); + } + + clickedFab() { + if (this.inContainer) { + const activated = !this.activated; + this.setActiveLists(activated); + } + } + + /** + * @hidden + */ + setActiveLists(activated: boolean) { + const lists = this.$el.parentElement.querySelectorAll('ion-fab-list') as NodeListOf; + + if (lists.length > 0) { + this.activated = activated; + } + + for (var i = 0; i < lists.length; i++) { + const list = lists[i].$instance; + list.activated = activated; + } + } + + /** + * Close an active FAB list container + */ + @Method() + close() { + this.setActiveLists(false); + } + + /** + * @hidden + * Get the element classes to add to the child element + */ + getElementClassList() { + let classList = [].concat( + this.$el.className.length ? this.$el.className.split(' ') : [] + ); + + return classList; + } + + /** + * @hidden + * Get the classes for fab buttons in lists + */ + getFabListClassList() { + if (!this.inList) { + return []; + } + return [ + `fab-in-list`, + `fab-${this.mode}-in-list` + ]; + } + + /** + * @hidden + * Get the close active class for fab buttons + */ + getFabActiveClassList() { + if (!this.activated) { + return []; + } + return [ + `fab-close-active` + ]; + } + + /** + * @hidden + * Get the show class for fab buttons + */ + getFabShowClassList() { + if (!this.show) { + return []; + } + return [ + `show` + ]; + } + + render() { + const themedClasses = createThemedClasses(this.mode, this.color, 'fab'); + + var fabClasses: CssClassObject = [] + .concat( + this.getElementClassList(), + this.getFabListClassList(), + this.getFabActiveClassList(), + this.getFabShowClassList() + ) + .reduce((prevValue, cssClass) => { + prevValue[cssClass] = true; + return prevValue; + }, {}); + + const TagType = this.href ? 'a' : 'button'; + + fabClasses = Object.assign(fabClasses, themedClasses); + + return ( + + + + + +
+
+ ); + } +} diff --git a/packages/core/src/components/fab/fab.wp.scss b/packages/core/src/components/fab/fab.wp.scss new file mode 100755 index 0000000000..fee699b283 --- /dev/null +++ b/packages/core/src/components/fab/fab.wp.scss @@ -0,0 +1,74 @@ +@import "../../themes/ionic.globals.wp"; +@import "./fab"; + +// Windows FAB Button +// -------------------------------------------------- + +/// @prop - Background color of the button +$fab-wp-background-color: color($colors-wp, primary) !default; + +/// @prop - Text color of the button +$fab-wp-text-color: color-contrast($colors-wp, $fab-wp-background-color) !default; + +/// @prop - Background color of the activated button +$fab-wp-background-color-activated: color-shade($fab-wp-background-color) !default; + +/// @prop - Background color of the button in a list +$fab-wp-list-button-background-color: $fab-list-button-background-color !default; + +/// @prop - Text color of the button in a list +$fab-wp-list-button-text-color: color-contrast($colors-wp, $fab-wp-list-button-background-color) !default; + +/// @prop - Background color of the activated button in a list +$fab-wp-list-button-background-color-activated: color-shade($fab-wp-list-button-background-color) !default; + +/// @prop - Transition duration of the transform and opacity of the button in a list +$fab-wp-list-button-transition-duration: 200ms !default; + +/// @prop - Speed curve of the transition of the transform and opacity of the button in a list +$fab-wp-list-button-transition-timing-function: ease !default; + +/// @prop - Transition delay of the transform and opacity of the button in a list +$fab-wp-list-button-transition-delay: 10ms !default; + + +.fab-wp { + color: $fab-wp-text-color; + background-color: $fab-wp-background-color; +} + +.fab-wp.activated { + background-color: $fab-wp-background-color-activated; +} + +.fab-wp-in-list { + color: $fab-wp-list-button-text-color; + background-color: $fab-wp-list-button-background-color; + + transition: transform $fab-wp-list-button-transition-duration $fab-wp-list-button-transition-timing-function $fab-wp-list-button-transition-delay, + opacity $fab-wp-list-button-transition-duration $fab-wp-list-button-transition-timing-function $fab-wp-list-button-transition-delay; +} + +.fab-wp-in-list.activated { + background-color: $fab-wp-list-button-background-color-activated; +} + + +// Generate WP FAB colors +// -------------------------------------------------- + +@each $color-name, $color-base, $color-contrast in get-colors($colors-wp) { + + $bg-color: $color-base; + $bg-color-activated: color-shade($bg-color); + $fg-color: $color-contrast; + + .fab-wp-#{$color-name} { + color: $fg-color; + background-color: $bg-color; + } + + .fab-wp-#{$color-name}.activated { + background-color: $bg-color-activated; + } +} diff --git a/packages/core/src/components/fab/test/basic.html b/packages/core/src/components/fab/test/basic.html new file mode 100644 index 0000000000..8ab83530af --- /dev/null +++ b/packages/core/src/components/fab/test/basic.html @@ -0,0 +1,145 @@ + + + + + Ionic FAB + + + + + + + + Header + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
log
+ Test +
+ + + + Footer + + + + + + +
+ + diff --git a/packages/core/stencil.config.js b/packages/core/stencil.config.js index 54019ffbbc..ec749c6c73 100644 --- a/packages/core/stencil.config.js +++ b/packages/core/stencil.config.js @@ -4,10 +4,11 @@ exports.config = { publicPath: '/dist', generateCollection: true, bundles: [ - { components: ['ion-app', 'ion-content', 'ion-footer', 'ion-header', 'ion-navbar', 'ion-page', 'ion-title', 'ion-toolbar'] }, + { components: ['ion-app', 'ion-content', 'ion-fixed', 'ion-footer', 'ion-header', 'ion-navbar', 'ion-page', 'ion-title', 'ion-toolbar'] }, { components: ['ion-avatar', 'ion-badge', 'ion-thumbnail'] }, { components: ['ion-button', 'ion-buttons', 'ion-icon'] }, { components: ['ion-card', 'ion-card-content', 'ion-card-header', 'ion-card-title'] }, + { components: ['ion-fab', 'ion-fab-button', 'ion-fab-list'] }, { components: ['ion-gesture', 'ion-scroll'], priority: 'low' }, { components: ['ion-item', 'ion-item-divider', 'ion-label', 'ion-list', 'ion-list-header', 'ion-skeleton-text'] }, { components: ['ion-loading', 'ion-loading-controller'] },