diff --git a/src/app/playground-components.ts b/src/app/playground-components.ts
index ec4a697804..d5e1598859 100644
--- a/src/app/playground-components.ts
+++ b/src/app/playground-components.ts
@@ -794,6 +794,18 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'RadioShowcaseComponent',
name: 'Radio Showcase',
},
+ {
+ path: 'radio-statuses.component',
+ link: '/radio/radio-statuses.component',
+ component: 'RadioStatusesComponent',
+ name: 'Radio Statuses',
+ },
+ {
+ path: 'radio-disabled-group.component',
+ link: '/radio/radio-disabled-group.component',
+ component: 'RadioDisabledGroupComponent',
+ name: 'Radio Disabled Group',
+ },
],
},
{
diff --git a/src/framework/theme/components/radio/_radio.component.theme.scss b/src/framework/theme/components/radio/_radio.component.theme.scss
index 78fcb955f4..26da5f118f 100644
--- a/src/framework/theme/components/radio/_radio.component.theme.scss
+++ b/src/framework/theme/components/radio/_radio.component.theme.scss
@@ -4,80 +4,66 @@
* 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-check-mark($size, $color) {
- &::before {
- background-color: $color;
- height: calc(#{$size} * 0.6);
- width: calc(#{$size} * 0.6);
- border: solid $color;
- }
-}
-
-@mixin set-box-style($bg, $size, $border-size, $border-color) {
- background-color: $bg;
- width: $size;
- height: $size;
- border: $border-size solid $border-color;
-}
-
@mixin nb-radio-theme() {
nb-radio {
- .radio-indicator {
- @include set-box-style(
- nb-theme(radio-bg),
- nb-theme(radio-size),
- nb-theme(radio-border-size),
- nb-theme(radio-border-color)
- );
-
- @include nb-radio-check-mark(nb-theme(radio-size), nb-theme(radio-checkmark));
+ .radio-circle {
+ height: nb-theme(radio-height);
+ width: nb-theme(radio-width);
+ background-color: nb-theme(radio-background-color);
+ border-style: nb-theme(radio-border-style);
+ border-width: nb-theme(radio-border-width);
}
- input:checked + .radio-indicator,
- input:disabled:checked + .radio-indicator {
- @include set-box-style(
- nb-theme(radio-checked-bg),
- nb-theme(radio-checked-size),
- nb-theme(radio-checked-border-size),
- nb-theme(radio-checked-border-color)
- );
- @include nb-radio-check-mark(nb-theme(radio-checked-size), nb-theme(radio-checked-checkmark));
+ .native-input:focus + .radio-circle {
+ box-shadow: 0 0 0 nb-theme(radio-outline-width) nb-theme(radio-outline-color);
}
- input:disabled + .radio-indicator {
- @include set-box-style(
- nb-theme(radio-disabled-bg),
- nb-theme(radio-disabled-size),
- nb-theme(radio-disabled-border-size),
- nb-theme(radio-disabled-border-color)
- );
- @include nb-radio-check-mark(nb-theme(radio-disabled-size), nb-theme(radio-disabled-checkmark));
+ .native-input:disabled + .radio-circle {
+ border-color: nb-theme(radio-disabled-border-color);
+ }
+ .native-input:disabled:checked + .radio-circle::before {
+ background-color: nb-theme(radio-disabled-inner-circle-color);
}
+ .native-input:disabled ~ .text {
+ color: nb-theme(radio-disabled-text-color);
+ }
+
+ @each $status in nb-get-statuses() {
+ &.status-#{$status} .native-input:enabled + .radio-circle {
+ border-color: nb-theme(radio-#{$status}-border-color);
+ }
+ &.status-#{$status} .native-input:enabled:checked + .radio-circle::before {
+ background-color: nb-theme(radio-#{$status}-inner-circle-color);;
+ }
+
+ &.status-#{$status} .native-input:enabled:focus + .radio-circle {
+ border-color: nb-theme(radio-#{$status}-focus-border-color);
+ }
+ &.status-#{$status} .native-input:enabled:checked:focus + .radio-circle::before {
+ background-color: nb-theme(radio-#{$status}-focus-inner-circle-color);
+ }
- @include nb-input-status-color(nb-theme(radio-checked-border-color));
+ &.status-#{$status} label:hover .native-input:enabled + .radio-circle {
+ border-color: nb-theme(radio-#{$status}-hover-border-color);
+ }
+ &.status-#{$status} label:hover .native-input:checked:enabled + .radio-circle::before {
+ background-color: nb-theme(radio-#{$status}-hover-inner-circle-color);
+ }
+
+ &.status-#{$status} .native-input:enabled:active + .radio-circle {
+ border-color: nb-theme(radio-#{$status}-active-border-color);
+ }
+ &.status-#{$status} .native-input:enabled:checked:active + .radio-circle::before {
+ background-color: nb-theme(radio-#{$status}-active-inner-circle-color);
+ }
+ }
- .radio-description {
- color: nb-theme(radio-fg);
+ .text {
+ color: nb-theme(radio-text-color);
+ font-family: nb-theme(radio-text-font-family);
+ font-size: nb-theme(radio-text-font-size);
+ font-weight: nb-theme(radio-text-font-weight);
+ line-height: nb-theme(radio-text-line-height);
}
}
}
diff --git a/src/framework/theme/components/radio/radio-group.component.ts b/src/framework/theme/components/radio/radio-group.component.ts
index 3c017f0772..69be77aec1 100644
--- a/src/framework/theme/components/radio/radio-group.component.ts
+++ b/src/framework/theme/components/radio/radio-group.component.ts
@@ -7,7 +7,6 @@
import {
AfterContentInit,
ChangeDetectionStrategy,
- ChangeDetectorRef,
Component,
ContentChildren,
EventEmitter,
@@ -23,11 +22,11 @@ import {
import { isPlatformBrowser } from '@angular/common';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { fromEvent, merge } from 'rxjs';
-import { filter, switchMap, take, takeWhile } from 'rxjs/operators';
+import { filter, switchMap, take, takeUntil, takeWhile } from 'rxjs/operators';
import { convertToBoolProperty } from '../helpers';
import { NB_DOCUMENT } from '../../theme.options';
import { NbRadioComponent } from './radio.component';
-
+import { NbComponentStatus } from '../component-status';
/**
* The `NbRadioGroupComponent` is the wrapper for `nb-radio` button.
@@ -35,9 +34,9 @@ import { NbRadioComponent } from './radio.component';
*
* ```html
*
- * Option 1
- * Option 2
- * Option 3
+ * Option 1
+ * Option 2
+ * Option 3
*
* ```
*
@@ -45,9 +44,9 @@ import { NbRadioComponent } from './radio.component';
*
* ```html
*
- * Option 1
- * Option 2
- * Option 3
+ * Option 1
+ * Option 2
+ * Option 3
*
* ```
*
@@ -59,13 +58,12 @@ import { NbRadioComponent } from './radio.component';
*
* ```
*
+ * You can change radio group status by setting `status` input.
+ * @stacked-example(Statuses, radio/radio-statuses.component)
+ *
* Also, you can disable the whole group using `disabled` attribute.
+ * @stacked-example(Disabled group, radio/radio-disabled-group.component)
*
- * ```html
- *
- * ...
- *
- * ```
* */
@Component({
selector: 'nb-radio-group',
@@ -82,48 +80,86 @@ import { NbRadioComponent } from './radio.component';
})
export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, ControlValueAccessor {
- @ContentChildren(NbRadioComponent, { descendants: true }) radios: QueryList;
+ protected alive: boolean = true;
+ protected isTouched: boolean = false;
+ protected onChange = (value: any) => {};
+ protected onTouched = () => {};
- @Input('value')
- set setValue(value: any) {
- this.value = value;
+ @Input()
+ get value(): any {
+ return this._value;
+ }
+ set value(value: any) {
+ this._value = value;
this.updateValues();
}
+ protected _value: any;
- @Input('name')
- set setName(name: string) {
- this.name = name;
+ @Input()
+ get name(): string {
+ return this._name;
+ }
+ set name(name: string) {
+ this._name = name;
this.updateNames();
}
+ protected _name: string;
- @Input('disabled')
- set setDisabled(disabled: boolean) {
- this.disabled = convertToBoolProperty(disabled);
+ @Input()
+ get disabled(): boolean {
+ return this._disabled;
+ }
+ set disabled(disabled: boolean) {
+ this._disabled = convertToBoolProperty(disabled);
this.updateDisabled();
}
+ protected _disabled: boolean;
- @Output() valueChange: EventEmitter = new EventEmitter();
+ /**
+ * Radio buttons status.
+ * Possible values are `primary` (default), `success`, `warning`, `danger`, `info`.
+ */
+ @Input()
+ get status(): NbComponentStatus {
+ return this._status;
+ }
+ set status(value: NbComponentStatus) {
+ if (this._status !== value) {
+ this._status = value;
+ this.updateStatus();
+ }
+ }
+ protected _status: NbComponentStatus = 'primary';
- protected disabled: boolean;
- protected value: any;
- protected name: string;
- protected alive: boolean = true;
- protected onChange = (value: any) => {};
- protected onTouched = () => {};
+ @ContentChildren(NbRadioComponent, { descendants: true }) radios: QueryList;
+
+ @Output() valueChange: EventEmitter = new EventEmitter();
constructor(
- protected cd: ChangeDetectorRef,
protected hostElement: ElementRef,
@Inject(PLATFORM_ID) protected platformId,
@Inject(NB_DOCUMENT) protected document,
) {}
ngAfterContentInit() {
+ // In case option 'name' isn't set on nb-radio component,
+ // we need to set it's name right away, so it won't overlap with options
+ // without names from other radio groups. Otherwise they all would have
+ // same name and will be considered as options from one group so only the
+ // last option will stay selected.
this.updateNames();
- this.updateValues();
- this.updateDisabled();
- this.subscribeOnRadiosValueChange();
- this.subscribeOnRadiosBlur();
+
+ Promise.resolve().then(() => this.updateAndSubscribeToRadios());
+
+ this.radios.changes
+ .pipe(takeWhile(() => this.alive))
+ .subscribe(() => {
+ // 'changes' emit during change detection run and we can't update
+ // option properties right of since they already was initialized.
+ // Instead we schedule microtask to update radios after change detection
+ // run is finished.
+ Promise.resolve().then(() => this.updateAndSubscribeToRadios());
+ });
}
ngOnDestroy() {
@@ -146,30 +182,43 @@ export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, Contr
}
}
+ protected updateAndSubscribeToRadios() {
+ this.updateNames();
+ this.updateValues();
+ this.updateDisabled();
+ this.updateStatus();
+ this.subscribeOnRadiosValueChange();
+ this.subscribeOnRadiosBlur();
+ }
+
protected updateNames() {
if (this.radios) {
this.radios.forEach((radio: NbRadioComponent) => radio.name = this.name);
- this.markRadiosForCheck();
}
}
protected updateValues() {
if (this.radios && typeof this.value !== 'undefined') {
this.radios.forEach((radio: NbRadioComponent) => radio.checked = radio.value === this.value);
- this.markRadiosForCheck();
}
}
protected updateDisabled() {
if (this.radios && typeof this.disabled !== 'undefined') {
- this.radios.forEach((radio: NbRadioComponent) => radio.setDisabled = this.disabled);
- this.markRadiosForCheck();
+ this.radios.forEach((radio: NbRadioComponent) => radio.disabled = this.disabled);
}
}
protected subscribeOnRadiosValueChange() {
+ if (!this.radios || !this.radios.length) {
+ return;
+ }
+
merge(...this.radios.map((radio: NbRadioComponent) => radio.valueChange))
- .pipe(takeWhile(() => this.alive))
+ .pipe(
+ takeWhile(() => this.alive),
+ takeUntil(this.radios.changes),
+ )
.subscribe((value: any) => {
this.writeValue(value);
this.propagateValue(value);
@@ -181,18 +230,16 @@ export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, Contr
this.onChange(value);
}
- protected markRadiosForCheck() {
- this.radios.forEach((radio: NbRadioComponent) => radio.markForCheck());
- }
-
protected subscribeOnRadiosBlur() {
- if (!isPlatformBrowser(this.platformId)) {
+ const hasNoRadios = !this.radios || !this.radios.length;
+ if (!isPlatformBrowser(this.platformId) || this.isTouched || hasNoRadios) {
return;
}
const hostElement = this.hostElement.nativeElement;
fromEvent(hostElement, 'focusin')
.pipe(
+ takeWhile(() => this.alive),
filter(event => hostElement.contains(event.target as Node)),
switchMap(() => merge(
fromEvent(this.document, 'focusin'),
@@ -200,7 +247,19 @@ export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, Contr
)),
filter(event => !hostElement.contains(event.target as Node)),
take(1),
+ takeUntil(this.radios.changes),
)
- .subscribe(() => this.onTouched());
+ .subscribe(() => this.markTouched());
+ }
+
+ protected markTouched() {
+ this.isTouched = true;
+ this.onTouched();
+ }
+
+ protected updateStatus() {
+ if (this.radios) {
+ this.radios.forEach((radio: NbRadioComponent) => radio.status = this.status);
+ }
}
}
diff --git a/src/framework/theme/components/radio/radio.component.scss b/src/framework/theme/components/radio/radio.component.scss
index afe7e64422..645cda9bcb 100644
--- a/src/framework/theme/components/radio/radio.component.scss
+++ b/src/framework/theme/components/radio/radio.component.scss
@@ -10,40 +10,30 @@
display: block;
label {
- position: relative;
display: inline-flex;
margin: 0;
min-height: inherit;
padding: 0.375rem 1.5rem 0.375rem 0;
+ align-items: center;
}
- input {
- position: absolute;
- opacity: 0;
-
- &:disabled {
- & + .radio-indicator,
- & ~ .radio-description {
- opacity: 0.5;
- }
- }
- }
-
- .radio-indicator {
+ .radio-circle {
border-radius: 50%;
+ border-style: solid;
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
+ }
- &::before {
- content: '';
- transition: all 0.1s;
- border-radius: 50%;
- }
+ .radio-circle::before {
+ content: '';
+ border-radius: 50%;
+ height: 75%;
+ width: 75%;
}
- .radio-description {
+ .text {
@include nb-ltr(padding-left, 0.5rem);
@include nb-rtl(padding-right, 0.5rem);
}
diff --git a/src/framework/theme/components/radio/radio.component.ts b/src/framework/theme/components/radio/radio.component.ts
index a8875a5d83..ae644b5e76 100644
--- a/src/framework/theme/components/radio/radio.component.ts
+++ b/src/framework/theme/components/radio/radio.component.ts
@@ -4,10 +4,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*/
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
+import {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ HostBinding,
+ Input,
+ Output,
+} from '@angular/core';
import { convertToBoolProperty } from '../helpers';
-
+import { NbComponentStatus } from '../component-status';
/**
* The `NbRadioComponent` provides the same functionality as native ``
@@ -22,7 +30,7 @@ import { convertToBoolProperty } from '../helpers';
* ```ts
* @NgModule({
* imports: [
- * // ...
+ * // ...
* NbRadioModule,
* ],
* })
@@ -35,9 +43,9 @@ import { convertToBoolProperty } from '../helpers';
*
* ```html
*
- * Option 1
- * Option 2
- * Option 3
+ * Option 1
+ * Option 2
+ * Option 3
*
* ```
*
@@ -48,22 +56,61 @@ import { convertToBoolProperty } from '../helpers';
*
* @styles
*
- * radio-bg
- * radio-fg
- * radio-size
- * radio-border-size
- * radio-border-color
- * radio-checkmark
- * radio-checked-bg
- * radio-checked-size
- * radio-checked-border-size
- * radio-checked-border-color
- * radio-checked-checkmark
- * radio-disabled-bg
- * radio-disabled-size
- * radio-disabled-border-size
- * radio-disabled-border-color
- * radio-disabled-checkmark
+ * radio-width
+ * radio-height:
+ * radio-background-color:
+ * radio-border-style:
+ * radio-border-width:
+ * radio-text-color:
+ * radio-text-font-family:
+ * radio-text-font-size:
+ * radio-text-font-weight:
+ * radio-text-line-height:
+ * radio-outline-color:
+ * radio-outline-width:
+ * radio-disabled-border-color:
+ * radio-disabled-text-color:
+ * radio-disabled-inner-circle-color:
+ * radio-primary-border-color:
+ * radio-primary-inner-circle-color:
+ * radio-primary-focus-border-color:
+ * radio-primary-focus-inner-circle-color:
+ * radio-primary-hover-border-color:
+ * radio-primary-hover-inner-circle-color:
+ * radio-primary-active-border-color:
+ * radio-primary-active-inner-circle-color:
+ * radio-success-border-color:
+ * radio-success-inner-circle-color:
+ * radio-success-focus-border-color:
+ * radio-success-focus-inner-circle-color:
+ * radio-success-hover-border-color:
+ * radio-success-hover-inner-circle-color:
+ * radio-success-active-border-color:
+ * radio-success-active-inner-circle-color:
+ * radio-warning-border-color:
+ * radio-warning-inner-circle-color:
+ * radio-warning-focus-border-color:
+ * radio-warning-focus-inner-circle-color:
+ * radio-warning-hover-border-color:
+ * radio-warning-hover-inner-circle-color:
+ * radio-warning-active-border-color:
+ * radio-warning-active-inner-circle-color:
+ * radio-danger-border-color:
+ * radio-danger-inner-circle-color:
+ * radio-danger-focus-border-color:
+ * radio-danger-focus-inner-circle-color:
+ * radio-danger-hover-border-color:
+ * radio-danger-hover-inner-circle-color:
+ * radio-danger-active-border-color:
+ * radio-danger-active-inner-circle-color:
+ * radio-info-border-color:
+ * radio-info-inner-circle-color:
+ * radio-info-focus-border-color:
+ * radio-info-focus-inner-circle-color:
+ * radio-info-hover-border-color:
+ * radio-info-hover-inner-circle-color:
+ * radio-info-active-border-color:
+ * radio-info-active-inner-circle-color:
* */
@Component({
selector: 'nb-radio',
@@ -71,14 +118,15 @@ import { convertToBoolProperty } from '../helpers';
@@ -88,28 +136,97 @@ import { convertToBoolProperty } from '../helpers';
})
export class NbRadioComponent {
- @Input() name: string;
+ @Input()
+ get name(): string {
+ return this._name;
+ }
+ set name(value: string) {
+ if (this._name !== value) {
+ this._name = value;
+ this.cd.detectChanges();
+ }
+ }
+ private _name: string;
+
+ @Input()
+ get checked(): boolean {
+ return this._checked;
+ }
+ set checked(value: boolean) {
+ const boolValue = convertToBoolProperty(value);
+ if (this._checked !== boolValue) {
+ this._checked = boolValue;
+ this.cd.markForCheck();
+ }
+ }
+ private _checked: boolean = false;
- @Input() checked: boolean;
+ @Input()
+ get value(): any {
+ return this._value;
+ }
+ set value(value: any) {
+ if (this._value !== value) {
+ this._value = value;
+ this.cd.markForCheck();
+ }
+ }
+ private _value: any;
- @Input() value: any;
+ @Input()
+ get disabled(): boolean {
+ return this._disabled;
+ }
+ set disabled(disabled: boolean) {
+ const boolValue = convertToBoolProperty(disabled);
+ if (this._disabled !== boolValue) {
+ this._disabled = boolValue;
+ this.cd.markForCheck();
+ }
+ }
+ private _disabled: boolean = false;
- @Input('disabled')
- set setDisabled(disabled: boolean) {
- this.disabled = convertToBoolProperty(disabled);
+ @Input()
+ get status(): NbComponentStatus {
+ return this._status;
}
+ set status(value: NbComponentStatus) {
+ if (this._status !== value) {
+ this._status = value;
+ this.cd.markForCheck();
+ }
+ }
+ private _status: NbComponentStatus = 'primary';
@Output() valueChange: EventEmitter = new EventEmitter();
@Output() blur: EventEmitter = new EventEmitter();
- disabled: boolean;
-
constructor(protected cd: ChangeDetectorRef) {}
- markForCheck() {
- this.cd.markForCheck();
- this.cd.detectChanges();
+ @HostBinding('class.status-primary')
+ get isPrimary(): boolean {
+ return this.status === 'primary';
+ }
+
+ @HostBinding('class.status-success')
+ get isSuccess(): boolean {
+ return this.status === 'success';
+ }
+
+ @HostBinding('class.status-warning')
+ get isWarning(): boolean {
+ return this.status === 'warning';
+ }
+
+ @HostBinding('class.status-danger')
+ get isDanger(): boolean {
+ return this.status === 'danger';
+ }
+
+ @HostBinding('class.status-info')
+ get isInfo(): boolean {
+ return this.status === 'info';
}
onChange(event: Event) {
diff --git a/src/framework/theme/components/radio/radio.spec.ts b/src/framework/theme/components/radio/radio.spec.ts
index 0ee3dbacb1..65e629a423 100644
--- a/src/framework/theme/components/radio/radio.spec.ts
+++ b/src/framework/theme/components/radio/radio.spec.ts
@@ -4,15 +4,26 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*/
-import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
+import {
+ Component,
+ DebugElement,
+ ElementRef,
+ EventEmitter,
+ Input,
+ Output,
+ QueryList,
+ ViewChild,
+ ViewChildren,
+} from '@angular/core';
+import { By } from '@angular/platform-browser';
+import createSpy = jasmine.createSpy;
import { NbRadioModule } from './radio.module';
import { NbRadioComponent } from './radio.component';
+import { NbRadioGroupComponent } from './radio-group.component';
import { NB_DOCUMENT } from '../../theme.options';
-import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core';
-import { By } from '@angular/platform-browser';
-
@Component({
selector: 'nb-radio-test',
template: `
@@ -28,11 +39,44 @@ export class NbRadioTestComponent {
@Output() valueChange = new EventEmitter();
}
+@Component({
+ template: `
+
+
+ {{radio}}
+
+
+ `,
+})
+export class NbRadioWithDynamicValuesTestComponent {
+ radioValues: number[] = [];
+ showRadios: boolean = false;
+
+ @ViewChild(NbRadioGroupComponent) radioGroupComponent: NbRadioGroupComponent;
+ @ViewChildren(NbRadioComponent) radioComponents: QueryList;
+}
+
+@Component({
+ template: `
+
+
+
+
+
+
+ `,
+})
+export class NbTwoRadioGroupsComponent {
+ @ViewChild('firstGroup', { read: NbRadioGroupComponent }) firstGroup: NbRadioGroupComponent;
+ @ViewChild('secondGroup', { read: NbRadioGroupComponent }) secondGroup: NbRadioGroupComponent;
+ @ViewChildren(NbRadioComponent, { read: ElementRef }) radios: QueryList;
+}
+
describe('radio', () => {
let fixture: ComponentFixture;
let comp: NbRadioTestComponent;
- beforeEach(() => {
+ beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [NbRadioModule],
declarations: [NbRadioTestComponent],
@@ -42,7 +86,9 @@ describe('radio', () => {
fixture = TestBed.createComponent(NbRadioTestComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
- });
+ flush();
+ fixture.detectChanges();
+ }));
it('should render radios', () => {
const radios: DebugElement[] = fixture.debugElement.queryAll(By.directive(NbRadioComponent));
@@ -56,3 +102,193 @@ describe('radio', () => {
input.nativeElement.click();
});
});
+
+describe('NbRadioGroupComponent', () => {
+ let fixture: ComponentFixture;
+ let radioTestComponent: NbRadioWithDynamicValuesTestComponent;
+
+ beforeEach(fakeAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [ NbRadioModule ],
+ declarations: [ NbRadioWithDynamicValuesTestComponent, NbTwoRadioGroupsComponent ],
+ providers: [ { provide: NB_DOCUMENT, useValue: document } ],
+ });
+
+ fixture = TestBed.createComponent(NbRadioWithDynamicValuesTestComponent);
+ radioTestComponent = fixture.componentInstance;
+ fixture.detectChanges();
+ flush(); // promise with 'updateAndSubscribeToRadios'
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+ }));
+
+ it('should update radio value when radios added after radio group initialization', fakeAsync(() => {
+ radioTestComponent.radioValues = [1, 2, 3];
+ radioTestComponent.showRadios = true;
+ radioTestComponent.radioGroupComponent.value = 1;
+ fixture.detectChanges(); // adds radios
+ flush(); // promise with 'updateAndSubscribeToRadios' in NbRadioGroup.radios.changes
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+
+ expect(radioTestComponent.radioComponents.first.checked).toEqual(true);
+ const otherRadios = radioTestComponent.radioComponents.toArray().slice(1);
+ for (const radio of otherRadios) {
+ expect(radio.checked).toEqual(false);
+ }
+ }));
+
+ it('should update radio name when radios added after radio group initialization', fakeAsync(() => {
+ const groupName = 'my-radio-group-name';
+ radioTestComponent.radioValues = [1, 2, 3];
+ radioTestComponent.showRadios = true;
+ radioTestComponent.radioGroupComponent.name = groupName;
+ fixture.detectChanges(); // adds radios
+ flush(); // promise with 'updateAndSubscribeToRadios' in NbRadioGroup.radios.changes
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+
+ for (const radio of radioTestComponent.radioComponents.toArray()) {
+ expect(radio.name).toEqual(groupName);
+ }
+ }));
+
+ it('should update radio disabled state when radios added after radio group initialization', fakeAsync(() => {
+ radioTestComponent.radioValues = [1, 2, 3];
+ radioTestComponent.showRadios = true;
+ radioTestComponent.radioGroupComponent.disabled = true;
+ fixture.detectChanges(); // adds radios
+ flush(); // promise with 'updateAndSubscribeToRadios' in NbRadioGroup.radios.changes
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+
+ for (const radio of radioTestComponent.radioComponents.toArray()) {
+ expect(radio.disabled).toEqual(true);
+ }
+ }));
+
+ it('should update radio status when radios added after radio group initialization', fakeAsync(() => {
+ radioTestComponent.radioValues = [1, 2, 3];
+ radioTestComponent.showRadios = true;
+ radioTestComponent.radioGroupComponent.status = 'info';
+ fixture.detectChanges(); // adds radios
+ flush(); // promise with 'updateAndSubscribeToRadios' in NbRadioGroup.radios.changes
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+
+ for (const radio of radioTestComponent.radioComponents.toArray()) {
+ expect(radio.status).toEqual('info');
+ }
+ }));
+
+ it('should update subscription to radio change when radios added after radio group initialization', fakeAsync(() => {
+ const radioValue = 333;
+ radioTestComponent.radioValues = [radioValue];
+ radioTestComponent.showRadios = true;
+ fixture.detectChanges(); // adds radios
+ flush(); // promise with 'updateAndSubscribeToRadios' in NbRadioGroup.radios.changes
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+
+ const valueChangeSpy = createSpy('valueChange');
+ radioTestComponent.radioGroupComponent.valueChange.subscribe(valueChangeSpy);
+ radioTestComponent.radioComponents.first.valueChange.emit(radioValue);
+
+ tick();
+
+ expect(valueChangeSpy).toHaveBeenCalledTimes(1);
+ expect(valueChangeSpy).toHaveBeenCalledWith(radioValue);
+ }));
+
+ it('should update radio value when radios change', fakeAsync(() => {
+ radioTestComponent.showRadios = true;
+ radioTestComponent.radioGroupComponent.value = 1;
+ fixture.detectChanges();
+
+ radioTestComponent.radioValues = [1, 2, 3];
+ fixture.detectChanges(); // adds radios
+ flush(); // promise with 'updateAndSubscribeToRadios' in NbRadioGroup.radios.changes
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+
+ expect(radioTestComponent.radioComponents.first.checked).toEqual(true);
+ const otherRadios = radioTestComponent.radioComponents.toArray().slice(1);
+ for (const radio of otherRadios) {
+ expect(radio.checked).toBeFalsy();
+ }
+ }));
+
+ it('should update radio name when radios change', fakeAsync(() => {
+ const groupName = 'my-radio-group-name';
+ radioTestComponent.showRadios = true;
+ radioTestComponent.radioGroupComponent.name = groupName;
+ fixture.detectChanges();
+
+ radioTestComponent.radioValues = [1, 2, 3];
+ fixture.detectChanges(); // adds radios
+ flush(); // promise with 'updateAndSubscribeToRadios' in NbRadioGroup.radios.changes
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+
+ for (const radio of radioTestComponent.radioComponents.toArray()) {
+ expect(radio.name).toEqual(groupName);
+ }
+ }));
+
+ it('should update radio disabled state when radios change', fakeAsync(() => {
+ radioTestComponent.showRadios = true;
+ radioTestComponent.radioGroupComponent.disabled = true;
+ fixture.detectChanges();
+
+ radioTestComponent.radioValues = [1, 2, 3];
+ fixture.detectChanges(); // adds radios
+ flush(); // promise with 'updateAndSubscribeToRadios' in NbRadioGroup.radios.changes
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+
+ for (const radio of radioTestComponent.radioComponents.toArray()) {
+ expect(radio.disabled).toEqual(true);
+ }
+ }));
+
+ it('should update radio status when radios change', fakeAsync(() => {
+ radioTestComponent.showRadios = true;
+ radioTestComponent.radioGroupComponent.status = 'info';
+ fixture.detectChanges();
+
+ radioTestComponent.radioValues = [1, 2, 3];
+ fixture.detectChanges(); // adds radios
+ flush(); // promise with 'updateAndSubscribeToRadios' in NbRadioGroup.radios.changes
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+
+ for (const radio of radioTestComponent.radioComponents.toArray()) {
+ expect(radio.status).toEqual('info');
+ }
+ }));
+
+ it('should update subscription to radio change when radios change', fakeAsync(() => {
+ const valueChangeSpy = createSpy('valueChange');
+ radioTestComponent.radioGroupComponent.valueChange.subscribe(valueChangeSpy);
+ radioTestComponent.showRadios = true;
+ fixture.detectChanges();
+ flush();
+ fixture.detectChanges();
+
+ const radioValue = 333;
+ radioTestComponent.radioValues = [radioValue];
+ fixture.detectChanges(); // adds radios
+ flush(); // promise with 'updateAndSubscribeToRadios' in NbRadioGroup.radios.changes
+ fixture.detectChanges(); // detect changes made during 'updateAndSubscribeToRadios'
+
+ radioTestComponent.radioComponents.first.valueChange.emit(radioValue);
+ tick();
+
+ expect(valueChangeSpy).toHaveBeenCalledTimes(1);
+ expect(valueChangeSpy).toHaveBeenCalledWith(radioValue);
+ }));
+
+ it(`should set options name right away so it won't overlap with options from another groups`, () => {
+ const radioFixture = TestBed.createComponent(NbTwoRadioGroupsComponent);
+ radioFixture.detectChanges();
+
+ const { firstGroup, secondGroup, radios } = radioFixture.componentInstance;
+ const radioFromFirstGroup = radios.first.nativeElement.querySelector('input');
+ const radioFromSecondGroup = radios.last.nativeElement.querySelector('input');
+
+ expect(firstGroup.radios.first.checked).toEqual(true);
+ expect(radioFromFirstGroup.checked).toEqual(true);
+ expect(secondGroup.radios.first.checked).toEqual(true);
+ expect(radioFromSecondGroup.checked).toEqual(true);
+ });
+});
diff --git a/src/framework/theme/styles/themes/_default.scss b/src/framework/theme/styles/themes/_default.scss
index cdf028888f..0fe7cdbca4 100644
--- a/src/framework/theme/styles/themes/_default.scss
+++ b/src/framework/theme/styles/themes/_default.scss
@@ -1008,22 +1008,67 @@ $theme: (
datepicker-shadow: none,
datepicker-arrow-size: 11px,
- radio-bg: transparent,
- radio-fg: color-fg-text,
- radio-size: 1.25rem,
- radio-border-size: 2px,
- radio-border-color: input-border-color,
- radio-checkmark: transparent,
- radio-checked-bg: transparent,
- radio-checked-size: 1.25rem,
- radio-checked-border-size: 2px,
- radio-checked-border-color: color-success,
- radio-checked-checkmark: color-success,
- radio-disabled-bg: transparent,
- radio-disabled-size: 1.25rem,
- radio-disabled-border-size: 2px,
- radio-disabled-border-color: radio-border-color,
- radio-disabled-checkmark: radio-checkmark,
+ radio-width: 1.125rem,
+ radio-height: 1.125rem,
+ radio-background-color: transparent,
+ radio-border-style: solid,
+ radio-border-width: 0.0625rem,
+ radio-text-color: text-dark-color,
+ radio-text-font-family: text-subtitle-font-family,
+ radio-text-font-size: text-subtitle-font-size,
+ radio-text-font-weight: text-subtitle-font-weight,
+ radio-text-line-height: text-subtitle-line-height,
+ radio-outline-color: outline-color,
+ radio-outline-width: outline-width,
+
+ radio-disabled-border-color: color-basic,
+ radio-disabled-text-color: text-disabled-color,
+ radio-disabled-inner-circle-color: color-basic,
+
+ radio-primary-border-color: color-primary,
+ radio-primary-inner-circle-color: color-primary,
+ radio-primary-focus-border-color: color-primary-focus,
+ radio-primary-focus-inner-circle-color: color-primary-focus,
+ radio-primary-hover-border-color: color-primary-hover,
+ radio-primary-hover-inner-circle-color: color-primary-hover,
+ radio-primary-active-border-color: color-primary-active,
+ radio-primary-active-inner-circle-color: color-primary-active,
+
+ radio-success-border-color: color-success,
+ radio-success-inner-circle-color: color-success,
+ radio-success-focus-border-color: color-success-focus,
+ radio-success-focus-inner-circle-color: color-success-focus,
+ radio-success-hover-border-color: color-success-hover,
+ radio-success-hover-inner-circle-color: color-success-hover,
+ radio-success-active-border-color: color-success-active,
+ radio-success-active-inner-circle-color: color-success-active,
+
+ radio-warning-border-color: color-warning,
+ radio-warning-inner-circle-color: color-warning,
+ radio-warning-focus-border-color: color-warning-focus,
+ radio-warning-focus-inner-circle-color: color-warning-focus,
+ radio-warning-hover-border-color: color-warning-hover,
+ radio-warning-hover-inner-circle-color: color-warning-hover,
+ radio-warning-active-border-color: color-warning-active,
+ radio-warning-active-inner-circle-color: color-warning-active,
+
+ radio-danger-border-color: color-danger,
+ radio-danger-inner-circle-color: color-danger,
+ radio-danger-focus-border-color: color-danger-focus,
+ radio-danger-focus-inner-circle-color: color-danger-focus,
+ radio-danger-hover-border-color: color-danger-hover,
+ radio-danger-hover-inner-circle-color: color-danger-hover,
+ radio-danger-active-border-color: color-danger-active,
+ radio-danger-active-inner-circle-color: color-danger-active,
+
+ radio-info-border-color: color-info,
+ radio-info-inner-circle-color: color-info,
+ radio-info-focus-border-color: color-info-focus,
+ radio-info-focus-inner-circle-color: color-info-focus,
+ radio-info-hover-border-color: color-info-hover,
+ radio-info-hover-inner-circle-color: color-info-hover,
+ radio-info-active-border-color: color-info-active,
+ radio-info-active-inner-circle-color: color-info-active,
tree-grid-cell-border-width: 1px,
tree-grid-cell-border-style: solid,
diff --git a/src/playground/with-layout/radio/radio-disabled-group.component.ts b/src/playground/with-layout/radio/radio-disabled-group.component.ts
new file mode 100644
index 0000000000..77454b5ab8
--- /dev/null
+++ b/src/playground/with-layout/radio/radio-disabled-group.component.ts
@@ -0,0 +1,24 @@
+import { Component } from '@angular/core';
+
+@Component({
+ template: `
+
+
+
+
+ {{ option.label }}
+
+
+
+
+ `,
+})
+export class RadioDisabledGroupComponent {
+ options = [
+ { value: 'This is value 1', label: 'Option 1' },
+ { value: 'This is value 2', label: 'Option 2' },
+ { value: 'This is value 3', label: 'Option 3' },
+ { value: 'This is value 4', label: 'Option 4' },
+ { value: 'This is value 5', label: 'Option 5' },
+ ];
+}
diff --git a/src/playground/with-layout/radio/radio-routing.module.ts b/src/playground/with-layout/radio/radio-routing.module.ts
index da68a8ee04..443cc7e4d4 100644
--- a/src/playground/with-layout/radio/radio-routing.module.ts
+++ b/src/playground/with-layout/radio/radio-routing.module.ts
@@ -8,6 +8,8 @@ import { NgModule } from '@angular/core';
import { RouterModule, Route} from '@angular/router';
import { RadioDisabledComponent } from './radio-disabled.component';
import { RadioShowcaseComponent } from './radio-showcase.component';
+import { RadioStatusesComponent } from './radio-statuses.component';
+import { RadioDisabledGroupComponent } from './radio-disabled-group.component';
const routes: Route[] = [
{
@@ -18,6 +20,14 @@ const routes: Route[] = [
path: 'radio-showcase.component',
component: RadioShowcaseComponent,
},
+ {
+ path: 'radio-statuses.component',
+ component: RadioStatusesComponent,
+ },
+ {
+ path: 'radio-disabled-group.component',
+ component: RadioDisabledGroupComponent,
+ },
];
@NgModule({
diff --git a/src/playground/with-layout/radio/radio-statuses-group.component.scss b/src/playground/with-layout/radio/radio-statuses-group.component.scss
new file mode 100644
index 0000000000..60622baa42
--- /dev/null
+++ b/src/playground/with-layout/radio/radio-statuses-group.component.scss
@@ -0,0 +1,8 @@
+:host {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+nb-radio-group {
+ padding: 1rem;
+}
diff --git a/src/playground/with-layout/radio/radio-statuses.component.ts b/src/playground/with-layout/radio/radio-statuses.component.ts
new file mode 100644
index 0000000000..fbdfe2697c
--- /dev/null
+++ b/src/playground/with-layout/radio/radio-statuses.component.ts
@@ -0,0 +1,25 @@
+import { Component } from '@angular/core';
+
+@Component({
+ template: `
+
+
+ {{ option.label }}
+
+
+ `,
+ styleUrls: ['./radio-statuses-group.component.scss'],
+})
+export class RadioStatusesComponent {
+ options = [
+ { value: 'This is value 1', label: 'Option 1', checked: true },
+ { value: 'This is value 2', label: 'Option 2' },
+ { value: 'This is value 3', label: 'Option 3' },
+ { value: 'This is value 4', label: 'Option 4', disabled: true },
+ ];
+
+ statuses = ['primary', 'success', 'warning', 'danger', 'info'];
+}
diff --git a/src/playground/with-layout/radio/radio.module.ts b/src/playground/with-layout/radio/radio.module.ts
index b64b27c19b..ef0cde6c92 100644
--- a/src/playground/with-layout/radio/radio.module.ts
+++ b/src/playground/with-layout/radio/radio.module.ts
@@ -11,11 +11,16 @@ import { NbCardModule, NbRadioModule } from '@nebular/theme';
import { RadioRoutingModule } from './radio-routing.module';
import { RadioDisabledComponent } from './radio-disabled.component';
import { RadioShowcaseComponent } from './radio-showcase.component';
+import { RadioStatusesComponent } from './radio-statuses.component';
+import { RadioDisabledGroupComponent } from './radio-disabled-group.component';
@NgModule({
declarations: [
RadioDisabledComponent,
RadioShowcaseComponent,
+ RadioDisabledComponent,
+ RadioStatusesComponent,
+ RadioDisabledGroupComponent,
],
imports: [
CommonModule,