iOS switch

This commit is contained in:
Adam Bradley
2015-07-28 14:16:45 -05:00
parent b24a98731f
commit 51516b8b64
16 changed files with 259 additions and 159 deletions

View File

@ -5,7 +5,6 @@
.checkbox {
position: relative;
display: block;
cursor: pointer;
@include user-select-none();
}
@ -21,6 +20,10 @@
@include appearance(none);
}
.checkbox .input-label {
max-width: 100%;
}
.checkbox[aria-disabled=true] {
opacity: 0.5;
color: gray;

View File

@ -18,7 +18,7 @@ import {Icon} from '../icon/icon';
@IonicComponent({
selector: 'ion-checkbox',
host: {
'[class.item]': 'item',
'class': 'item',
'[attr.aria-checked]': 'input.checked'
}
})
@ -46,8 +46,6 @@ export class Checkbox extends IonInputItem {
this.cd = cd;
cd.valueAccessor = this;
this.item = true;
}
onInit() {

View File

@ -2,14 +2,12 @@
// Label
// --------------------------------------------------
$input-label-color: #444 !default;
$input-label-color: #888 !default;
.input-label {
display: block;
max-width: 200px;
//width: 30%;
//min-width: 100px;
color: $input-label-color;
font-size: inherit;
white-space: nowrap;

View File

@ -5,6 +5,7 @@ import * as dom from '../../util/dom';
import {Input} from './text-input';
import {Checkbox} from '../checkbox/checkbox';
import {RadioButton} from '../radio/radio';
import {Switch} from '../switch/switch';
@Directive({
@ -23,9 +24,10 @@ export class Label {
@Optional() @Parent() textContainer: Input,
@Optional() @Parent() checkboxContainer: Checkbox,
@Optional() @Parent() radioContainer: RadioButton,
@Optional() @Parent() switchContainer: Switch,
config: IonicConfig
) {
this.container = textContainer || checkboxContainer || radioContainer;
this.container = textContainer || checkboxContainer || radioContainer || switchContainer;
if (this.container) {
this.container.registerLabel(this);

View File

@ -19,6 +19,9 @@
<ion-input>
<label class="fixed-inline-label">From</label>
<input value="Text 3" type="text">
<button primary clear>
<icon name="ion-power"></icon>
</button>
</ion-input>
<ion-input>
@ -27,16 +30,19 @@
</ion-input>
<ion-input>
<icon name="ion-earth"></icon>
<label class="fixed-inline-label">Website</label>
<input value="http://ionic.io/" type="url">
</ion-input>
<ion-input>
<icon name="ion-email"></icon>
<label class="fixed-inline-label">Email</label>
<input value="email6@email.com" type="email">
</ion-input>
<ion-input>
<icon name="ion-earth"></icon>
<label class="fixed-inline-label">Feedback</label>
<textarea placeholder="Placeholder Text"></textarea>
</ion-input>
@ -49,6 +55,7 @@
<ion-input>
<label class="fixed-inline-label">Score</label>
<input value="10" type="number">
<button primary outline>Update</button>
</ion-input>
<ion-input>

View File

@ -19,6 +19,9 @@
<ion-input>
<label>From:</label>
<input value="Text 3" type="text">
<button primary clear>
<icon name="ion-power"></icon>
</button>
</ion-input>
<ion-input>
@ -27,16 +30,19 @@
</ion-input>
<ion-input>
<icon name="ion-earth"></icon>
<label>Website:</label>
<input value="http://ionic.io/" type="url">
</ion-input>
<ion-input>
<icon name="ion-email"></icon>
<label>Email:</label>
<input value="email6@email.com" type="email">
</ion-input>
<ion-input>
<icon name="ion-edit"></icon>
<label>Feedback:</label>
<textarea placeholder="Placeholder Text"></textarea>
</ion-input>
@ -44,11 +50,13 @@
<ion-input>
<label>More Info:</label>
<input placeholder="Placeholder Text" type="text">
<icon name="ion-flag"></icon>
</ion-input>
<ion-input>
<label>Score:</label>
<input value="10" type="number">
<button primary outline>Update</button>
</ion-input>
<ion-input>

View File

@ -83,6 +83,10 @@ $item-ios-note-color: #999 !default;
font-size: 1.3rem;
}
.item-input .input + button {
margin-top: $item-ios-padding-media-top;
}
.badge {
margin-right: $item-ios-padding-right;
}

View File

@ -73,6 +73,8 @@ button.item.item {
.item-content + .item-media,
.item-content + .item-content,
icon + .input,
.input + icon,
icon + .input-label,
.input-label + .input {
margin-left: 0;
}

View File

@ -71,7 +71,7 @@ export class RadioGroup extends Ion {
@IonicComponent({
selector: 'ion-radio',
host: {
'[class.item]': 'item',
'class': 'item',
'[attr.aria-checked]': 'input.checked',
}
})
@ -91,7 +91,6 @@ export class RadioButton extends IonInputItem {
config: IonicConfig
) {
super(elementRef, config);
this.item = true;
this.group = group;
}

View File

@ -2,37 +2,86 @@
// iOS Switch
// --------------------------------------------------
$switch-ios-width: 52px !default;
$switch-ios-height: 32px !default;
$switch-ios-slider-off-background: #fff !default;
$switch-ios-slider-on-background: #4cd964 !default;
$switch-ios-toggle-on-background: #fff !default;
$switch-ios-width: 51px !default;
$switch-ios-height: 31px !default;
$switch-ios-border-width: 2px !default;
$switch-ios-border-radius: 30px !default;
$switch-ios-off-bg-color: #fff !default;
$switch-ios-off-border-color: #e6e6e6 !default;
$switch-ios-on-bg-color: get-color(primary, base) !default;
$switch-ios-on-border-color: $switch-ios-on-bg-color !default;
$switch-ios-handle-width: $switch-ios-height - ($switch-ios-border-width * 2) !default;
$switch-ios-handle-height: $switch-ios-handle-width !default;
$switch-ios-handle-radius: $switch-ios-handle-width !default;
$switch-ios-handle-dragging-bg-color: darken(#fff, 5%) !default;
$switch-ios-handle-box-shadow: 0 3px 12px rgba(0, 0, 0, 0.16), 0 3px 1px rgba(0, 0, 0, 0.1), 0px 0px 1px rgba(0, 0, 0, 0.15) !default;
$switch-ios-handle-off-bg-color: #fff !default;
$switch-ios-handle-on-bg-color: #fff !default;
$switch-ios-hit-area-expansion: 5px !default;
.switch[mode="ios"] {
.switch-toggle {
.media-switch {
margin-top: 5px;
margin-bottom: 5px;
}
.switch-track {
/*@include transition-timing-function(ease-in-out);
@include transition-duration($switch-ios-transition-duration);
@include transition-property((background-color, border));*/
position: relative;
width: $switch-ios-width;
height: $switch-ios-height;
border-radius: $switch-ios-height / 2;
background: #e5e5e5;
border: solid $switch-ios-border-width $switch-ios-off-border-color;
border-radius: $switch-ios-border-radius;
background-color: $switch-ios-off-bg-color;
content: ' ';
cursor: pointer;
pointer-events: none;
}
.switch-toggle:before {
background: $switch-ios-slider-off-background;
.switch-handle {
//@include transition($switch-ios-transition-duration cubic-bezier(0, 1.1, 1, 1.1));
//@include transition-property((background-color, transform));
position: absolute;
width: $switch-ios-handle-width;
height: $switch-ios-handle-height;
border-radius: $switch-ios-handle-radius;
background-color: $switch-ios-handle-off-bg-color;
top: 0;
left: 0;
box-shadow: $switch-ios-handle-box-shadow;
&:before {
position: absolute;
top: -4px;
left: ( ($switch-ios-handle-width / 2) * -1) - 8;
padding: ($switch-ios-handle-height / 2) + 5 ($switch-ios-handle-width + 7);
content: '';
}
}
.switch-toggle:after {
box-shadow: 0 2px 5px rgba(0,0,0,.4);
&[aria-checked=true] .switch-track {
background-color: $switch-ios-on-bg-color;
border-color: $switch-ios-on-border-color;
}
&[aria-checked=true] .switch-toggle {
background: $switch-ios-slider-on-background;
&[aria-checked=true] .switch-handle {
background-color: $switch-ios-handle-on-bg-color;
transform: translate3d($switch-ios-width - $switch-ios-handle-width - ($switch-ios-border-width * 2), 0, 0);
}
&[aria-checked=true] .switch-toggle:before {
transform: scale(0);
.input-label {
color: inherit;
}
}

View File

@ -2,73 +2,29 @@
// Switch
// --------------------------------------------------
$switch-padding: 0 15px !default;
$switch-width: 52px !default;
$switch-height: 32px !default;
$switch-border-width: 2px !default;
$switch-slider-off-background: #ccc !default;
$switch-slider-on-background: #387ef5 !default;
$switch-toggle-on-background: #fff !default;
.switch .item-media {
padding: $switch-padding;
}
.switch-toggle {
.switch {
position: relative;
width: $switch-width;
height: $switch-height;
border-radius: $switch-height / 2;
background: $switch-slider-off-background;
cursor: pointer;
@include user-select-none();
}
.switch-toggle:before {
.switch input {
position: absolute;
left: $switch-border-width;
top: $switch-border-width;
width: $switch-width - ($switch-border-width * 2);
height: $switch-height - ($switch-border-width * 2);
border-radius: $switch-height / 2;
transition-duration: 300ms;
content: ' ';
width: 0;
height: 0;
margin: 0;
padding: 0;
opacity: 0;
border: none;
@include appearance(none);
}
.switch[aria-checked=true] .switch-toggle {
background: $switch-slider-on-background;
}
.switch .switch-toggle:after {
position: absolute;
left: $switch-border-width;
top: $switch-border-width;
width: $switch-height - ($switch-border-width * 2);
height: $switch-height - ($switch-border-width * 2);
border-radius: $switch-height - ($switch-border-width * 2);
background: $switch-toggle-on-background;
transition-duration: 300ms;
content: ' ';
}
.switch[aria-checked=true] .switch-toggle:after {
transform: translate3d(20px,0,0);
.switch .input-label {
max-width: 100%;
}
.switch[aria-disabled=true] {
pointer-events: none;
opacity: 0.5;
color: gray;
}
.switch .item-media,
.switch .item-content {
pointer-events: none;
}

View File

@ -1,71 +1,127 @@
import {View, ElementRef} from 'angular2/angular2';
import {ControlGroup, ControlDirective} from 'angular2/forms';
import {
View,
Directive,
ElementRef,
Renderer,
Optional,
Parent,
NgControl
} from 'angular2/angular2';
import {Ion} from '../ion';
import {IonInputItem} from '../form/input';
import {IonicConfig} from '../../config/config';
import {IonicComponent} from '../../config/annotations';
import {IonicComponent, IonicView} from '../../config/annotations';
import {Icon} from '../icon/icon';
@IonicComponent({
selector: 'ion-switch',
properties: [
'checked'
],
host: {
'(click)': 'switchClicked($event)',
'class': 'item'
'class': 'item',
//'[attr.aria-checked]': 'input.checked'
}
})
@View({
template: `
<div class="item-content">
<div class="item-title">
<content></content>
</div>
<div class="item-media media-switch">
<div class="switch-toggle"></div>
</div>
</div>`
@IonicView({
template:
'<div class="item-content">' +
'<content></content>' +
'</div>' +
'<div class="item-media media-switch">' +
'<div class="switch-track">' +
'<div class="switch-handle"></div>' +
'</div>' +
'</div>'
})
export class Switch extends Ion {
export class Switch extends IonInputItem {
constructor(
@Optional() cd: NgControl,
renderer: Renderer,
elementRef: ElementRef,
ionicConfig: IonicConfig
//cd: ControlDirective
config: IonicConfig
) {
super(elementRef, ionicConfig)
super(elementRef, config);
this.onChange = (_) => {};
this.onTouched = (_) => {};
this.renderer = renderer;
this.elementRef = elementRef;
this.cd = cd;
// this.config = Switch.config.invoke(this)
// this.controlDirective = cd;
// cd.valueAccessor = this;
// TODO: These rely on the commented-out PropertySetter's above
//setAriaRole('checkbox')
//setInvalid('false')
//setDisabled('false')
//this.setCheckedProperty = setAriaChecked
if(cd) cd.valueAccessor = this;
}
/**
* Much like ngModel, this is called from our valueAccessor for the attached
* ControlDirective to update the value internally.
*/
writeValue(value) {
// Convert it to a boolean
this.checked = !!value;
onInit() {
super.onInit();
console.log("switch onInit")
}
set checked(checked) {
this._checked = checked
//this.setCheckedProperty(checked)
this.controlDirective._control().updateValue(this._checked);
onAllChangesDone() {
return
console.log("switch onAllChangesDone")
if (this._checked !== void 0 && this.input.checked != this._checked) {
if (this.input.checked !== void 0) {
console.warn("switch checked is set in view template and Control declaration.\n" +
"Value: " + !!this._checked + " from Control takes precedence");
}
this.input.checked = !!this._checked;
}
if (this._value !== void 0 && this.input.value != this._value) {
if (this.input.value !== void 0) {
console.warn("switch value is set in view template and Control declaration.\n" +
"Value: " + this._value + " from Control takes precedence");
}
this.input.value = this._value;
}
if (this.input.value === void 0) {
this.input.value = "on";
}
if (this.input.checked === void 0) {
this.input.checked = false;
}
//TODO check validity
this.cd.control._value = {"checked": !!this.input.checked, "value": this.input.value};
//TODO only want to call this once, we want to set input.checked directly on subsequent
// writeValue's
this.onAllChangesDone = () => {};
// this.onChange({"checked": this.input.checked, "value": this.input.value});
}
get checked() {
return this._checked
//from clicking the label or selecting with keyboard
//view -> model (Control)
toggle() {
this.input.checked = this._checked = !this.input.checked;
this.onChange({"checked": this.input.checked, "value": this.input.value});
}
switchClicked(ev) {
this.checked = !this.checked;
// Called by the model (Control) to update the view
writeValue(modelValue) {
let type = typeof modelValue;
switch (type) {
case "boolean":
// don't set input.value here, do it in onAllChangesDone
// because they might have set it in the view
this._checked = modelValue; break;
case "object":
if (modelValue.checked !== void 0) this._checked = !!modelValue.checked;
if (modelValue.value !== void 0) this._value = modelValue.value.toString();
break;
default:
// don't set input.checked here, do it in onAllChangesDone
// because they might have set it in the view
this._value = modelValue.toString();
}
//TODO we want to set input.checked directly after the first time
console.log("writeValue, " + this.input.id + " checked: " + this._checked);
console.log("writeValue " + this.input.id + " value: " + this._value);
// this.cd.control._value = {"checked": this.input.checked, "value": this.input.value};
}
// Used by the view to update the model (Control)
// Up to us to call it in update()
registerOnChange(fn) { this.onChange = fn; }
registerOnTouched(fn) { this.onTouched = fn; }
}

View File

@ -1,24 +1,32 @@
import {FormBuilder, Validators} from 'angular2/forms';
import {App} from 'ionic/ionic';
import {
Control,
ControlGroup,
NgForm,
formDirectives,
Validators,
NgControl,
ControlValueAccessor,
NgControlName,
NgFormModel,
FormBuilder
} from 'angular2/forms';
@App({
templateUrl: 'main.html'
})
class IonicApp {
constructor() {
var fb = new FormBuilder();
this.form = fb.group({
enableFun: ['', Validators.required],
enableIceCream: [false, Validators.required],
enablePizza: [true, Validators.required]
this.fruitsForm = new ControlGroup({
"appleCtrl": new Control({"checked": false, "value": "apple"}),
"bananaCtrl": new Control(true),
"cherryCtrl": new Control({"checked": false, "value": 12}),
"grapeCtrl": new Control("grape")
});
}
doSubmit(ev) {
console.log('Submitting form', this.form.value);
ev.preventDefault();
console.log('Submitting form', this.fruitsForm.value);
event.preventDefault();
}
}

View File

@ -1,25 +1,35 @@
<ion-toolbar><ion-title>Switches</ion-title></ion-toolbar>
<ion-content>
<form (^submit)="doSubmit($event)" [control-group]="form">
<form (^submit)="doSubmit($event)">
<ion-list>
<ion-switch aria-checked="true">
<label id="appleLabel">Apple</label>
<input checked="true" type="checkbox">
</ion-switch>
<ion-switch>
<label>Banana</label>
<input value="test" type="checkbox">
</ion-switch>
<ion-switch aria-checked="true">
<label>Cherry</label>
<input type="checkbox">
</ion-switch>
<ion-switch>
<label>Grape</label>
<input value="test" checked="checked" type="checkbox">
</ion-switch>
<ion-list>
<div class="list-header">Some Switches</div>
<ion-switch control="enableFun">
Enable Fun?
</ion-switch>
<ion-switch control="enableIceCream">
Enable Ice Cream?
</ion-switch>
<ion-switch control="enablePizza">
Enable Pizza?
</ion-switch>
</ion-list>
Is fun enabled? <b>{{form.controls.enableFun.value}}</b>
<br>
Is ice cream enabled? <b>{{form.controls.enableIceCream.value}}</b>
<br>
Is pizza enabled? <b>{{form.controls.enablePizza.value}}</b>
</form>
</ion-content>

View File

@ -65,7 +65,7 @@ export const IonicDirectives = [
forwardRef(() => Checkbox),
forwardRef(() => RadioGroup),
forwardRef(() => RadioButton),
//Switch
forwardRef(() => Switch),
//SearchBar,
// Input