Skip to content

Commit

Permalink
feat(theme): add new Radio component (#746)
Browse files Browse the repository at this point in the history
  • Loading branch information
tibing-old-email authored and nnixaa committed Sep 25, 2018
1 parent b585490 commit 9e04681
Show file tree
Hide file tree
Showing 16 changed files with 657 additions and 3 deletions.
9 changes: 9 additions & 0 deletions docs/structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,15 @@ export const structure = [
'NbCheckboxComponent',
],
},
{
type: 'tabs',
name: 'Radio',
icon: 'radio.svg',
source: [
'NbRadioComponent',
'NbRadioGroupComponent',
],
},
{
type: 'tabs',
name: 'Select',
Expand Down
163 changes: 163 additions & 0 deletions src/framework/theme/components/radio/_radio.component.theme.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

@mixin input-border-color($color) {
input:checked ~ .radio-indicator, input:hover:not(:disabled) ~ .radio-indicator {
border-color: $color;
}
}

@mixin nb-input-status-color($origin-border-color) {
@include input-border-color($origin-border-color);
&.success {
@include input-border-color(nb-theme(color-success));
}
&.warning {
@include input-border-color(nb-theme(color-warning));
}
&.danger {
@include input-border-color(nb-theme(color-danger));
}
}

@mixin nb-radio($size, $color) {
&::before {
content: '';
position: absolute;
background-color: $color;
height: calc(#{$size} * 0.6);
width: calc(#{$size} * 0.6);
border: solid $color;
border-radius: 50%;
}
}

@mixin set-style($bg, $size, $border-size, $border-color) {
background-color: $bg;
width: $size;
height: $size;
border: $border-size solid $border-color;
}

@mixin description-style {
color: nb-theme(radio-fg);
padding-left: 0.25rem;
}

@mixin nb-radio-theme() {
nb-radio {
display: block;

label {
position: relative;
display: inline-flex;
margin: 0;
min-height: inherit;
padding: 0.375rem 1.5rem;
}

.radio-indicator {
@include set-style(
nb-theme(radio-bg),
nb-theme(radio-size),
nb-theme(radio-border-size),
nb-theme(radio-border-color)
);
border-radius: 50%;
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
flex: none;
display: flex;
justify-content: center;
align-items: center;

@include nb-radio(nb-theme(radio-size), nb-theme(radio-checkmark));

&::before {
width: 0;
height: 0;
transition: all 0.1s;
}
}

input {
position: absolute;
opacity: 0;
z-index: -1;

&:checked ~ .radio-indicator {
@include set-style(
nb-theme(radio-checked-bg),
nb-theme(radio-checked-size),
nb-theme(radio-checked-border-size),
nb-theme(radio-checked-border-color)
);
display: flex;
justify-content: center;
align-items: center;
@include nb-radio(
nb-theme(radio-checked-size),
nb-theme(radio-checked-checkmark)
);
}

&:disabled ~ {
.radio-indicator {
@include set-style(
nb-theme(radio-disabled-bg),
nb-theme(radio-disabled-size),
nb-theme(radio-disabled-border-size),
nb-theme(radio-border-color)
);
opacity: 0.5;
display: flex;
justify-content: center;
align-items: center;
@include nb-radio(
nb-theme(radio-disabled-size),
nb-theme(radio-checkmark)
);
}

.radio-description {
opacity: 0.5;
@include description-style;
}
}

&:disabled:checked ~ {
.radio-indicator {
@include set-style(
nb-theme(radio-disabled-bg),
nb-theme(radio-disabled-size),
nb-theme(radio-disabled-border-size),
nb-theme(radio-checked-border-color)
);
opacity: 0.5;
display: flex;
justify-content: center;
align-items: center;
@include nb-radio(
nb-theme(radio-disabled-size),
nb-theme(radio-disabled-checkmark)
);
}
.radio-description {
opacity: 0.5;
@include description-style;
}
}
}

@include nb-input-status-color(nb-theme(radio-checked-border-color));

.radio-description {
@include description-style;
}
}
}
174 changes: 174 additions & 0 deletions src/framework/theme/components/radio/radio-group.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import {
AfterContentInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChildren,
EventEmitter,
forwardRef,
Input,
OnDestroy,
Output,
QueryList,
} from '@angular/core';
import { NbRadioComponent } from './radio.component';
import { merge } from 'rxjs';
import { takeWhile } from 'rxjs/operators';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { convertToBoolProperty } from '../helpers';


/**
* The `NbRadioGroupComponent` is the wrapper for `nb-radio` button.
* It provides form bindings:
*
* ```html
* <nb-radio-group [(ngModule)]="selectedOption">
* <nb-radio>Option 1</nb-radio>
* <nb-radio>Option 2</nb-radio>
* <nb-radio>Option 3</nb-radio>
* </nb-radio-group>
* ```
*
* Also, you can use `value` and `valueChange` for binding without forms.
*
* ```html
* <nb-radio-group [(value)]="selectedOption">
* <nb-radio>Option 1</nb-radio>
* <nb-radio>Option 2</nb-radio>
* <nb-radio>Option 3</nb-radio>
* </nb-radio-group>
* ```
*
* Radio items name has to be provided through `name` input property of the radio group.
*
* ```html
* <nb-radio-group name="my-radio-group">
* ...
* </nb-radio-group>
* ```
*
* Also, you can disable the whole group using `disabled` attribute.
*
* ```html
* <nb-radio-group disabled>
* ...
* </nb-radio-group>
* ```
* */
@Component({
selector: 'nb-radio-group',
template: `
<ng-content select="nb-radio"></ng-content>`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NbRadioGroupComponent),
multi: true,
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, ControlValueAccessor {

@ContentChildren(NbRadioComponent, { descendants: true }) radios: QueryList<NbRadioComponent>;

@Input('value')
set setValue(value: any) {
this.value = value;
this.updateValues();
}

@Input('name')
set setName(name: string){
this.name = name;
this.updateNames();
}

@Input('disabled')
set setDisabled(disabled: boolean) {
this.disabled = convertToBoolProperty(disabled);
this.updateDisabled();
}

@Output() valueChange: EventEmitter<any> = new EventEmitter();

protected disabled: boolean;
protected value: any;
protected name: string;
protected alive: boolean = true;
protected onChange = (value: any) => {};

constructor(protected cd: ChangeDetectorRef) {}

ngAfterContentInit() {
this.updateNames();
this.updateValues();
this.updateDisabled();
this.subscribeOnRadiosValueChange();
}

ngOnDestroy() {
this.alive = false;
}

registerOnChange(fn: any): void {
this.onChange = fn;
}

registerOnTouched(fn: any): void {
}

writeValue(value: any): void {
this.value = value;
}

protected updateNames() {
if (this.radios) {
this.radios.forEach((radio: NbRadioComponent) => radio.name = this.name);
this.markRadiosForCheck();
}
}

protected updateValues() {
if (this.radios) {
this.radios.forEach((radio: NbRadioComponent) => radio.checked = radio.value === this.value);
this.markRadiosForCheck();
}
}

protected updateDisabled() {
if (this.radios) {
this.radios.forEach((radio: NbRadioComponent) => {
if (!radio.hasOwnProperty('disabled')) {
radio.setDisabled = this.disabled
}
});
this.markRadiosForCheck();
}
}

protected subscribeOnRadiosValueChange() {
merge(...this.radios.map((radio: NbRadioComponent) => radio.valueChange))
.pipe(takeWhile(() => this.alive))
.subscribe((value: any) => {
this.writeValue(value);
this.propagateValue(value);
});
}

protected propagateValue(value: any) {
this.valueChange.emit(value);
this.onChange(value);
}

protected markRadiosForCheck() {
this.radios.forEach((radio: NbRadioComponent) => radio.markForCheck());
}
}
Loading

0 comments on commit 9e04681

Please sign in to comment.