Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(radio): Eva style #1344

Merged
merged 31 commits into from
Apr 5, 2019
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
544237a
feat(radio): eva styles
yggg Apr 2, 2019
267670b
refactor(radio): align class names with checkbox
yggg Mar 24, 2019
b07dbe9
fix(radio): update and resubscribe to option after they change
yggg Mar 24, 2019
3138b24
fix(radio): update and resubscribe to option after they change
yggg Mar 24, 2019
3dd3cd7
feat(radio): add statuses to radio button
yggg Mar 24, 2019
00a4ce2
refactor(radio): style using updated component properties
yggg Mar 24, 2019
409b149
fix(radio): prevent inner circle distortion
yggg Mar 24, 2019
37a550a
docs(radio): add statuses and disabled group examples
yggg Mar 24, 2019
79931f2
docs(radio): update theme properties list
yggg Mar 24, 2019
fc04ebf
fix(radio): proper typography settings
yggg Mar 31, 2019
0eb812f
refactor(radio): remove white status
yggg Mar 31, 2019
89df6bd
feat(radio): add background color
yggg Mar 31, 2019
5476810
refactor(radio): remove named inputs
yggg Mar 31, 2019
7496c65
refactor(radio): remove own status enum and use shared
yggg Mar 31, 2019
f725667
fix(radio): typo
yggg Mar 31, 2019
1458ec0
docs(radio): fix examples
yggg Mar 31, 2019
94d2f2b
feat(radio): add border style theme property
yggg Apr 2, 2019
218f14e
refactor(radio): use named state colors
yggg Apr 2, 2019
60e60c6
feat(radio): prevent setting empty status
yggg Apr 2, 2019
c0abb37
docs(radio): update theme properties list
yggg Apr 3, 2019
d09570e
refactor(radio): move status class to the host
yggg Apr 3, 2019
42baf59
feat(radio): set default status if value not passed
yggg Apr 3, 2019
09b5f3a
fix(radio): update radio status and trigger change detection
yggg Apr 3, 2019
19e5fe7
refactor(radio): set provided status if it differ from current
yggg Apr 3, 2019
b43cc88
refactor(radio): call change detector methods from radio component
yggg Apr 3, 2019
32003be
fix(radio): set radio name right away
yggg Apr 3, 2019
54852e9
Merge branch 'next' into feat/eva-radio
yggg Apr 4, 2019
3275354
fix(radio): prevent subscription creating if no options
yggg Apr 4, 2019
5c2b1d7
feat(radio): initialize radio properties
yggg Apr 4, 2019
bae04b9
fix(radio): wait change detection to finish before updating options
yggg Apr 4, 2019
8533560
docs(radio): remove unnecessary interface
yggg Apr 4, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
],
},
{
Expand Down
116 changes: 51 additions & 65 deletions src/framework/theme/components/radio/_radio.component.theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
.native-input:enabled + .radio-circle.status-#{$status} {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we need the same empty check as for checkbox?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need, nb-get-status has no empty statuses only 'primary', 'success', 'warning', 'danger', 'info'.

border-color: nb-theme(radio-#{$status}-border-color);
}
.native-input:enabled:checked + .radio-circle.status-#{$status}::before {
background-color: nb-theme(radio-#{$status}-inner-circle-color);;
}

.native-input:enabled:focus + .radio-circle.status-#{$status} {
border-color: nb-theme(radio-#{$status}-focus-border-color);
}
.native-input:enabled:checked:focus + .radio-circle.status-#{$status}::before {
background-color: nb-theme(radio-#{$status}-focus-inner-circle-color);
}

@include nb-input-status-color(nb-theme(radio-checked-border-color));
label:hover .native-input:enabled + .radio-circle.status-#{$status} {
border-color: nb-theme(radio-#{$status}-hover-border-color);
}
label:hover .native-input:checked:enabled + .radio-circle.status-#{$status}::before {
background-color: nb-theme(radio-#{$status}-hover-inner-circle-color);
}

.native-input:enabled:active + .radio-circle.status-#{$status} {
border-color: nb-theme(radio-#{$status}-active-border-color);
}
.native-input:enabled:checked:active + .radio-circle.status-#{$status}::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);
}
}
}
134 changes: 95 additions & 39 deletions src/framework/theme/components/radio/radio-group.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,31 @@ 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.
* It provides form bindings:
*
* ```html
* <nb-radio-group [(ngModel)]="selectedOption">
* <nb-radio>Option 1</nb-radio>
* <nb-radio>Option 2</nb-radio>
* <nb-radio>Option 3</nb-radio>
* <nb-radio value="1">Option 1</nb-radio>
* <nb-radio value="2">Option 2</nb-radio>
* <nb-radio value="3">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 value="1">Option 1</nb-radio>
* <nb-radio value="2">Option 2</nb-radio>
* <nb-radio value="3">Option 3</nb-radio>
* </nb-radio-group>
* ```
*
Expand All @@ -59,13 +59,12 @@ import { NbRadioComponent } from './radio.component';
* </nb-radio-group>
* ```
*
* 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
* <nb-radio-group disabled>
* ...
* </nb-radio-group>
* ```
* */
@Component({
selector: 'nb-radio-group',
Expand All @@ -82,34 +81,63 @@ import { NbRadioComponent } from './radio.component';
})
export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, ControlValueAccessor {

@ContentChildren(NbRadioComponent, { descendants: true }) radios: QueryList<NbRadioComponent>;
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<any> = new EventEmitter();
/**
* Radio buttons status.
* Possible values are `primary` (default), `success`, `warning`, `danger`, `info`.
*/
@Input()
get status(): NbComponentStatus {
return this._status;
}
set status(status: NbComponentStatus) {
if (status === '') {
this._status = this._defaultStatus;
} else {
this._status = status;
}
this.updateStatus();
}
protected readonly _defaultStatus: NbComponentStatus = 'primary';
protected _status: NbComponentStatus = this._defaultStatus;

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<NbRadioComponent>;

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

constructor(
protected cd: ChangeDetectorRef,
Expand All @@ -119,11 +147,11 @@ export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, Contr
) {}

ngAfterContentInit() {
this.updateNames();
this.updateValues();
this.updateDisabled();
this.subscribeOnRadiosValueChange();
this.subscribeOnRadiosBlur();
this.updateAndSubscribeToRadios();

this.radios.changes
.pipe(takeWhile(() => this.alive))
.subscribe(() => this.updateAndSubscribeToRadios());
}

ngOnDestroy() {
Expand All @@ -146,6 +174,15 @@ 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);
Expand All @@ -162,14 +199,17 @@ export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, Contr

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

protected subscribeOnRadiosValueChange() {
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);
Expand All @@ -186,21 +226,37 @@ export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, Contr
}

protected subscribeOnRadiosBlur() {
if (!isPlatformBrowser(this.platformId)) {
if (!isPlatformBrowser(this.platformId) || this.isTouched) {
return;
}

const hostElement = this.hostElement.nativeElement;
fromEvent<Event>(hostElement, 'focusin')
.pipe(
takeWhile(() => this.alive),
filter(event => hostElement.contains(event.target as Node)),
switchMap(() => merge(
fromEvent<Event>(this.document, 'focusin'),
fromEvent<Event>(this.document, 'click'),
)),
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;
radio.markForCheck();
});
}
}
}
Loading