feat(segment): add segment component

This commit is contained in:
Brandy Carney
2017-06-30 14:40:33 -04:00
parent 77665fca7f
commit caf6dee959
8 changed files with 800 additions and 0 deletions

View File

@ -0,0 +1,125 @@
import { Component, h, Ionic, Prop, State } from '@stencil/core';
import { CssClassObject } from '../../util/interfaces';
import { createThemedClasses } from '../../util/theme';
/**
* @name SegmentButton
* @description
* The child buttons of the `ion-segment` component. Each `ion-segment-button` must have a value.
*
* @usage
*
* ```html
* <ion-content>
* <!-- Segment buttons with icons -->
* <ion-segment [(ngModel)]="icons" color="secondary">
* <ion-segment-button value="camera">
* <ion-icon name="camera"></ion-icon>
* </ion-segment-button>
* <ion-segment-button value="bookmark">
* <ion-icon name="bookmark"></ion-icon>
* </ion-segment-button>
* </ion-segment>
*
* <!-- Segment buttons with text -->
* <ion-segment [(ngModel)]="relationship" color="primary">
* <ion-segment-button value="friends" (ionSelect)="selectedFriends()">
* Friends
* </ion-segment-button>
* <ion-segment-button value="enemies" (ionSelect)="selectedEnemies()">
* Enemies
* </ion-segment-button>
* </ion-segment>
* </ion-content>
* ```
*
*
* @demo /docs/demos/src/segment/
* @see {@link /docs/components#segment Segment Component Docs}
* @see {@link /docs/api/components/segment/Segment/ Segment API Docs}
*/
@Component({
tag: 'ion-segment-button'
})
export class SegmentButton {
styleTmr: any;
mode: string;
color: string;
@State() activated: boolean = false;
/*
* @input {boolean} If true, the button is selected. Default false.
*/
@Prop({ state: true }) checked: boolean = false;
/*
* @input {boolean} If true, the user cannot interact with this element. Default false.
*/
@Prop({ state: true }) disabled: boolean = false;
/**
* @input {string} the value of the segment button. Required.
*/
@Prop({ state: true }) value: string;
segmentButtonClick(ev: UIEvent) {
ev.preventDefault();
ev.stopPropagation();
console.log('in segment button click');
this.emitClick();
}
/**
* Emit the click event to the parent segment
*/
private emitClick() {
clearTimeout(this.styleTmr);
this.styleTmr = setTimeout(() => {
Ionic.emit(this, 'ionClick', {
detail: {
'segmentButton': this,
}
});
});
}
/**
* @hidden
* Get the element classes to add to the child element
*/
getElementClassList() {
let classList = [].concat(
this.disabled ? 'segment-button-disabled' : [],
this.activated ? 'segment-activated' : [],
);
return classList;
}
render() {
const segmentButtonCss = createThemedClasses(this.mode, this.color, 'segment-button');
var segmentButtonClasses: CssClassObject = []
.concat(
this.getElementClassList()
)
.reduce((prevValue, cssClass) => {
prevValue[cssClass] = true;
return prevValue;
}, {});
segmentButtonClasses = Object.assign(segmentButtonClasses, segmentButtonCss);
return [
<button onClick={this.segmentButtonClick.bind(this)} class={segmentButtonClasses} aria-pressed={this.activated}>
<slot></slot>
</button>
];
}
}

View File

@ -0,0 +1,231 @@
@import "../../themes/ionic.globals.ios";
@import "./segment";
// iOS Segment
// --------------------------------------------------
/// @prop - Background of the segment button
$segment-button-ios-background-color: transparent !default;
/// @prop - Background of the activated segment button
$segment-button-ios-background-color-activated: $toolbar-ios-active-color !default;
/// @prop - Text color of the segment button
$segment-button-ios-text-color: color-contrast($colors-ios, $segment-button-ios-background-color-activated) !default;
/// @prop - Transition of the activated segment button
$segment-button-ios-transition-activated: 100ms all linear !default;
/// @prop - Transition of the segment button on hover
$segment-button-ios-transition-hover: 100ms all linear !default;
/// @prop - Transition of the segment button when pressed
$segment-button-ios-transition-active: 100ms all linear !default;
/// @prop - Opacity of the segment button on hover
$segment-button-ios-opacity-hover: .1 !default;
/// @prop - Opacity of the segment button when pressed
$segment-button-ios-opacity-active: .16 !default;
/// @prop - Opacity of the activated segment button
$segment-button-ios-opacity-activated: 1 !default;
/// @prop - Opacity of the disabled segment button
$segment-button-ios-opacity-disabled: .3 !default;
/// @prop - Border width of the segment button
$segment-button-ios-border-width: 1px !default;
/// @prop - Height of the segment button
$segment-button-ios-height: 3.2rem !default;
/// @prop - Line height of the segment button
$segment-button-ios-line-height: 2.8rem !default;
/// @prop - Font size of the segment button
$segment-button-ios-font-size: 1.3rem !default;
/// @prop - Border radius of the segment button
$segment-button-ios-border-radius: 4px !default;
/// @prop - Size of an icon in the segment button
$segment-button-ios-icon-size: 2.6rem !default;
/// @prop - Line height of an icon in the segment button
$segment-button-ios-icon-line-height: 2.8rem !default;
/// @prop - Max width of the segment button in a toolbar
$segment-button-ios-toolbar-button-max-width: 100px !default;
/// @prop - Height of the segment button in a toolbar
$segment-button-ios-toolbar-button-height: 2.6rem !default;
/// @prop - Line height of the segment button in a toolbar
$segment-button-ios-toolbar-line-height: 2.2rem !default;
/// @prop - Font size of the segment button in a toolbar
$segment-button-ios-toolbar-font-size: 1.2rem !default;
/// @prop - Size of an icon in the segment button in a toolbar
$segment-button-ios-toolbar-icon-size: 2.2rem !default;
/// @prop - Line height of an icon in the segment button in a toolbar
$segment-button-ios-toolbar-icon-line-height: 2.4rem !default;
.segment-ios ion-segment-button {
display: flex;
flex: 1;
width: 0;
&:first-of-type .segment-button {
@include border-radius($segment-button-ios-border-radius, 0, 0, $segment-button-ios-border-radius);
@include margin-horizontal(null, 0);
}
&:not(:first-of-type) .segment-button {
border-left-width: 0;
}
&:last-of-type .segment-button {
@include border-radius(0, $segment-button-ios-border-radius, $segment-button-ios-border-radius, 0);
@include margin-horizontal(0, null);
border-left-width: 0;
}
}
.segment-ios .segment-button {
flex: 1;
height: $segment-button-ios-height;
border-width: $segment-button-ios-border-width;
border-style: solid;
border-color: $segment-button-ios-background-color-activated;
font-size: $segment-button-ios-font-size;
line-height: $segment-button-ios-line-height;
color: $segment-button-ios-background-color-activated;
background-color: $segment-button-ios-background-color;
ion-icon {
font-size: $segment-button-ios-icon-size;
line-height: $segment-button-ios-icon-line-height;
}
&.segment-activated {
color: $segment-button-ios-text-color;
background-color: $segment-button-ios-background-color-activated;
opacity: $segment-button-ios-opacity-activated;
transition: $segment-button-ios-transition-activated;
}
&:hover:not(.segment-activated) {
background-color: rgba($segment-button-ios-background-color-activated, $segment-button-ios-opacity-hover);
transition: $segment-button-ios-transition-hover;
}
&:active:not(.segment-activated) {
background-color: rgba($segment-button-ios-background-color-activated, $segment-button-ios-opacity-active);
transition: $segment-button-ios-transition-active;
}
}
[dir="rtl"] .segment-ios ion-segment-button {
&:first-of-type .segment-button {
border-left-width: 0;
}
&:last-of-type .segment-button {
border-left-width: $segment-button-ios-border-width;
}
}
.segment-ios.segment-disabled {
opacity: .4;
pointer-events: none;
}
.segment-ios .segment-button-disabled {
color: rgba($segment-button-ios-background-color-activated, $segment-button-ios-opacity-disabled);
pointer-events: none;
}
// iOS Segment in Toolbar
// --------------------------------------------------
.toolbar-ios .segment-ios {
@include position(0, 0, 0, 0);
position: absolute;
}
.toolbar-ios ion-segment-button {
max-width: $segment-button-ios-toolbar-button-max-width;
}
.toolbar-ios .segment-button {
height: $segment-button-ios-toolbar-button-height;
font-size: $segment-button-ios-toolbar-font-size;
line-height: $segment-button-ios-toolbar-line-height;
ion-icon {
font-size: $segment-button-ios-toolbar-icon-size;
line-height: $segment-button-ios-toolbar-icon-line-height;
}
}
// iOS Segment Button Mixin
// --------------------------------------------------
@mixin ios-segment-button($color-name, $color-base, $color-contrast) {
.segment-ios-#{$color-name} {
.segment-button {
border-color: $color-base;
color: $color-base;
&:hover:not(.segment-activated) {
background-color: rgba($color-base, $segment-button-ios-opacity-hover);
}
&:active:not(.segment-activated) {
background-color: rgba($color-base, $segment-button-ios-opacity-active);
}
&.segment-activated {
color: $color-contrast;
background-color: $color-base;
}
}
.segment-button-disabled {
color: rgba($color-base, $segment-button-ios-opacity-disabled);
}
}
}
// iOS Segment Color Generation
// --------------------------------------------------
@each $color-name, $color-base, $color-contrast in get-colors($colors-ios) {
@include ios-segment-button($color-name, $color-base, $color-contrast);
.toolbar-ios-#{$color-name} .segment-ios .segment-button.segment-activated {
color: $color-base;
}
}

View File

@ -0,0 +1,145 @@
@import "../../themes/ionic.globals.md";
@import "./segment";
// Material Design Segment
// --------------------------------------------------
/// @prop - Text color of the activated segment button
$segment-button-md-text-color-activated: $toolbar-md-active-color !default;
/// @prop - Border color of the activated segment button
$segment-button-md-border-color-activated: $toolbar-md-active-color !default;
/// @prop - Width of the bottom border on the segment button
$segment-button-md-border-bottom-width: 2px !default;
/// @prop - Color of the bottom border on the segment button
$segment-button-md-border-bottom-color: rgba(#000, .10) !default;
/// @prop - Opacity of the segment button
$segment-button-md-opacity: .7 !default;
/// @prop - Opacity of the activated segment button
$segment-button-md-opacity-activated: 1 !default;
/// @prop - Opacity of the disabled segment button
$segment-button-md-opacity-disabled: .3 !default;
// deprecated
$segment-button-md-padding: null !default;
/// @prop - Padding top of the segment button
$segment-button-md-padding-top: 0 !default;
/// @prop - Padding end of the segment button
$segment-button-md-padding-end: 6px !default;
/// @prop - Padding bottom of the segment button
$segment-button-md-padding-bottom: $segment-button-md-padding-top !default;
/// @prop - Padding start of the segment button
$segment-button-md-padding-start: $segment-button-md-padding-end !default;
/// @prop - Height of the segment button
$segment-button-md-height: 4.2rem !default;
/// @prop - Line height of the segment button
$segment-button-md-line-height: 4rem !default;
/// @prop - Font size of the segment button
$segment-button-md-font-size: 1.2rem !default;
/// @prop - Size of an icon in the segment button
$segment-button-md-icon-size: 2.6rem !default;
/// @prop - Line height of an icon in the segment button
$segment-button-md-icon-line-height: $segment-button-md-line-height !default;
.segment-md ion-segment-button {
display: flex;
flex: 1;
}
.segment-md .segment-button {
flex: 1;
width: 0;
height: $segment-button-md-height;
border-bottom-width: $segment-button-md-border-bottom-width;
border-bottom-style: solid;
border-bottom-color: $segment-button-md-border-bottom-color;
font-size: $segment-button-md-font-size;
font-weight: 500;
line-height: $segment-button-md-line-height;
text-transform: uppercase;
color: $segment-button-md-text-color-activated;
background-color: transparent;
opacity: $segment-button-md-opacity;
transition: 100ms all linear;
@include deprecated-variable(padding, $segment-button-md-padding) {
@include padding($segment-button-md-padding-top, $segment-button-md-padding-end, $segment-button-md-padding-bottom, $segment-button-md-padding-start);
}
ion-icon {
font-size: $segment-button-md-icon-size;
line-height: $segment-button-md-icon-line-height;
}
&.activated,
&.segment-activated {
border-color: $segment-button-md-border-color-activated;
opacity: $segment-button-md-opacity-activated;
}
}
.segment-md.segment-disabled,
.segment-md .segment-button-disabled {
opacity: $segment-button-md-opacity-disabled;
pointer-events: none;
}
.toolbar {
.segment-md {
@include margin(0, auto);
}
.segment-md .segment-button.activated,
.segment-md .segment-button.segment-activated {
opacity: 1;
}
}
// Material Design Segment Button Mixin
// --------------------------------------------------
@mixin md-segment-button($color-name, $color-base, $color-contrast) {
.segment-md-#{$color-name} .segment-button {
color: $color-base;
&.activated,
&.segment-activated {
border-color: $color-base;
color: $color-base;
opacity: 1;
}
}
}
// Material Design Segment Color Generation
// --------------------------------------------------
@each $color-name, $color-base, $color-contrast in get-colors($colors-md) {
@include md-segment-button($color-name, $color-base, $color-contrast);
}

View File

@ -0,0 +1,28 @@
@import "../../themes/ionic.globals";
// Segment
// --------------------------------------------------
ion-segment {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
width: 100%;
}
.segment-button {
@include margin-horizontal(0);
@include text-align(center);
position: relative;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}

View File

@ -0,0 +1,130 @@
import { Component, h, Listen, Prop, PropDidChange } from '@stencil/core';
import { SegmentButtonEvent, VNodeData } from '../../util/interfaces';
/**
* @name Segment
* @description
* A Segment is a group of buttons, sometimes known as Segmented Controls, that allow the user to interact with a compact group of a number of controls.
* Segments provide functionality similar to tabs, selecting one will unselect all others. You should use a tab bar instead of a segmented control when you want to let the user move back and forth between distinct pages in your app.
* You could use Angular's `ngModel` or `FormBuilder` API. For an overview on how `FormBuilder` works, checkout [Angular Forms](http://learnangular2.com/forms/), or [Angular FormBuilder](https://angular.io/docs/ts/latest/api/forms/index/FormBuilder-class.html)
*
*
* ```html
* <!-- Segment in a header -->
* <ion-header>
* <ion-toolbar>
* <ion-segment [(ngModel)]="icons" color="secondary">
* <ion-segment-button value="camera">
* <ion-icon name="camera"></ion-icon>
* </ion-segment-button>
* <ion-segment-button value="bookmark">
* <ion-icon name="bookmark"></ion-icon>
* </ion-segment-button>
* </ion-segment>
* </ion-toolbar>
* </ion-header>
*
* <ion-content>
* <!-- Segment in content -->
* <ion-segment [(ngModel)]="relationship" color="primary" (ionChange)="segmentChanged($event)">
* <ion-segment-button value="friends">
* Friends
* </ion-segment-button>
* <ion-segment-button value="enemies">
* Enemies
* </ion-segment-button>
* </ion-segment>
*
* <!-- Segment in a form -->
* <form [formGroup]="myForm">
* <ion-segment formControlName="mapStyle" color="danger">
* <ion-segment-button value="standard">
* Standard
* </ion-segment-button>
* <ion-segment-button value="hybrid">
* Hybrid
* </ion-segment-button>
* <ion-segment-button value="sat">
* Satellite
* </ion-segment-button>
* </ion-segment>
* </form>
* </ion-content>
* ```
*
*
* @demo /docs/demos/src/segment/
* @see {@link /docs/components#segment Segment Component Docs}
* @see [Angular Forms](http://learnangular2.com/forms/)
*/
@Component({
tag: 'ion-segment',
styleUrls: {
ios: 'segment.ios.scss',
md: 'segment.md.scss',
wp: 'segment.wp.scss'
},
host: {
theme: 'segment'
}
})
export class Segment {
buttons: any;
$el: any;
@Prop({ state: true }) disabled: boolean = false;
@Prop({ state: true }) value: string;
@PropDidChange('value')
changed(val: string) {
this.selectButton(val);
}
ionViewDidLoad() {
this.buttons = this.$el.querySelectorAll('ion-segment-button');
for (var i = 0; i < this.buttons.length; i++) {
const button = this.buttons[i].$instance;
button.activated = (button.value === this.value);
// If there is no value set on the segment and a button
// is checked we should activate it
if (!this.value && button.checked) {
button.activated = button.checked;
}
}
}
@Listen('ionClick')
segmentClick(ev: SegmentButtonEvent) {
let selectedButton = ev.detail.segmentButton;
this.value = selectedButton.value;
this.selectButton(this.value);
}
selectButton(val: string) {
for (var i = 0; i < this.buttons.length; i++) {
const button = this.buttons[i].$instance;
button.activated = (button.value === val);
}
// returning true tells the renderer to queue an update
return true;
}
hostData(): VNodeData {
return {
class: {
'segment-disabled': this.disabled
}
};
}
render() {
return <slot></slot>;
}
}

View File

@ -0,0 +1,133 @@
@import "../../themes/ionic.globals.wp";
@import "./segment";
// Windows Segment
// --------------------------------------------------
/// @prop - Background of the segment button
$segment-button-wp-background-color: transparent !default;
/// @prop - Text color of the activated segment button
$segment-button-wp-text-color-activated: $toolbar-wp-text-color !default;
// deprecated
$segment-button-wp-padding: null !default;
/// @prop - Padding top of the segment button
$segment-button-wp-padding-top: 0 !default;
/// @prop - Padding end of the segment button
$segment-button-wp-padding-end: 6px !default;
/// @prop - Padding bottom of the segment button
$segment-button-wp-padding-bottom: $segment-button-wp-padding-top !default;
/// @prop - Padding start of the segment button
$segment-button-wp-padding-start: $segment-button-wp-padding-end !default;
/// @prop - Height of the segment button
$segment-button-wp-height: 4rem !default;
/// @prop - Line height of the segment button
$segment-button-wp-line-height: 4rem !default;
/// @prop - Font size of the segment button
$segment-button-wp-font-size: 1.3rem !default;
/// @prop - Text transform of the segment button
$segment-button-wp-text-transform: uppercase !default;
/// @prop - Font weight of the segment button
$segment-button-wp-font-weight: bold !default;
/// @prop - Opacity of the segment button
$segment-button-wp-opacity: .5 !default;
/// @prop - Opacity of the activated segment button
$segment-button-wp-opacity-activated: 1 !default;
/// @prop - Opacity of the disabled segment button
$segment-button-wp-opacity-disabled: .3 !default;
/// @prop - Size of an icon in the segment button
$segment-button-wp-icon-size: 2.6rem !default;
/// @prop - Line height of an icon in the segment button
$segment-button-wp-icon-line-height: $segment-button-wp-line-height !default;
/// @prop - Position of the buttons in the segment
$segment-button-wp-buttons-justify-content: flex-start !default;
.segment-wp {
justify-content: $segment-button-wp-buttons-justify-content;
}
.segment-wp .segment-button {
height: $segment-button-wp-height;
font-size: $segment-button-wp-font-size;
font-weight: $segment-button-wp-font-weight;
line-height: $segment-button-wp-line-height;
text-transform: $segment-button-wp-text-transform;
color: $segment-button-wp-text-color-activated;
background-color: $segment-button-wp-background-color;
opacity: $segment-button-wp-opacity;
@include deprecated-variable(padding, $segment-button-wp-padding) {
@include padding($segment-button-wp-padding-top, $segment-button-wp-padding-end, $segment-button-wp-padding-bottom, $segment-button-wp-padding-start);
}
&.segment-activated {
opacity: $segment-button-wp-opacity-activated;
}
ion-icon {
font-size: $segment-button-wp-icon-size;
line-height: $segment-button-wp-icon-line-height;
}
}
.segment-wp.segment-disabled,
.segment-wp .segment-button-disabled {
opacity: $segment-button-wp-opacity-disabled;
pointer-events: none;
}
.toolbar {
.segment-wp {
@include margin(0, auto);
}
}
// Windows Segment Button Mixin
// --------------------------------------------------
@mixin wp-segment-button($color-name, $color-base) {
.segment-wp-#{$color-name} .segment-button {
color: $color-base;
&.activated,
&.segment-activated {
border-color: $color-base;
color: $color-base;
opacity: $segment-button-wp-opacity-activated;
}
}
}
// Windows Segment Color Generation
// --------------------------------------------------
@each $color-name, $color-base, $color-contrast in get-colors($colors-wp) {
@include wp-segment-button($color-name, $color-base);
}

View File

@ -211,6 +211,13 @@ export interface GestureCallback {
}
export interface SegmentButtonEvent {
detail: {
segmentButton: any;
};
}
export interface ScrollDetail extends GestureDetail {
scrollTop?: number;
scrollLeft?: number;

View File

@ -11,6 +11,7 @@ exports.config = {
{ components: ['ion-loading', 'ion-loading-controller'] },
{ components: ['ion-menu'], priority: 'low' },
{ components: ['ion-modal', 'ion-modal-controller'] },
{ components: ['ion-segment', 'ion-segment-button'] },
{ components: ['ion-slides', 'ion-slide'] },
{ components: ['ion-spinner'] },
{ components: ['ion-toggle'] }