Skip to content

Commit

Permalink
feat(theme): ability to add custom statuses (#2625)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
Empty string won't be converted to `basic` status anymore. If you used to set `status` properties to `''` change it to `'basic'`.

`NbComponentStatus` type replaced with `NbComponentOrCustomStatus` in following properties: `NbActionComponent.badgeStatus`, `NbBadge.status`, `NbBadgeComponent.status`, `NbButtonComponent.status`, `NbAlertComponent.status`, `NbCardComponent.status`, `NbChatFormComponent.status`, `NbChatComponent.status`, `NbCheckboxComponent.status`, `NbFormFieldControl.status$`, `NbFormControlState.status`, `NbInputDirective.status`, `NbInputDirective.status%`, `NbSelectComponent.status`, `NbSelectComponent.status$`, `NbIconConfig.status`, `NbIconComponent.status`, `NbProgressBarComponent.status`, `NbRadioComponent.status`, `NbRadioGroupComponent.status`, `NbSpinnerDirective.spinnerStatus`, `NbSpinnerComponent.status`, `NbTabComponent.badgeStatus`, `NbToastrConfig.status`, `NbToggleComponent.status`, `NbTooltipDirective.status`, `NbTooltipComponent.context.status`, `NbUserComponent.badgeStatus`.
If you extended listed components replace `NbComponentStatus` in the property type with `NbComponentOrCustomStatus`.

`NbBadgeComponent`, `NbButtonComponent`, `NbAlertComponent`, `NbCardComponent`, `NbChatComponent`, `NbCheckboxComponent`, `NbInputDirective`, `NbSelectComponent`, `NbIconComponent`, `NbProgressBarComponent`, `NbRadioComponent`, `NbSpinnerComponent`, `NbToastComponent`, `NbToggleComponent`, `NbTooltipComponent` constructors now require `NbStatusService` as a constructor parameter. If you extended listed components, inject `NbStatusService` and pass it to the base class.
  • Loading branch information
yggg authored Dec 17, 2020
1 parent 1ead242 commit 3b2e903
Show file tree
Hide file tree
Showing 56 changed files with 387 additions and 240 deletions.
4 changes: 2 additions & 2 deletions src/framework/bootstrap/styles/_button-group.scss
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
}
}

@each $status in nb-get-statuses() {
@each $status in nb-get-core-statuses() {
.btn-group:not(.btn-divided-group) {
.btn.btn-outline-#{$status}.active {
background-color: nb-theme(button-filled-#{$status}-active-background-color);
Expand Down Expand Up @@ -244,7 +244,7 @@
}

.btn-divided-group {
@each $status in nb-get-statuses() {
@each $status in nb-get-core-statuses() {
.btn-#{$status} {
background-color: nb-theme(button-filled-#{$status}-background-color);
border-color: nb-theme(button-filled-#{$status}-border-color);
Expand Down
6 changes: 3 additions & 3 deletions src/framework/theme/components/actions/actions.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Component, HostBinding, Input } from '@angular/core';

import { convertToBoolProperty, NbBooleanInput } from '../helpers';
import { NbComponentSize } from '../component-size';
import { NbComponentStatus } from '../component-status';
import { NbComponentOrCustomStatus } from '../component-status';
import { NbBadgePosition } from '../badge/badge.component';
import { NbIconConfig } from '../icon/icon.component';

Expand Down Expand Up @@ -121,10 +121,10 @@ export class NbActionComponent {

/**
* Badge status (adds specific styles):
* 'primary', 'info', 'success', 'warning', 'danger'
* 'basic', 'primary', 'info', 'success', 'warning', 'danger', 'control'
* @param {string} val
*/
@Input() badgeStatus: NbComponentStatus = 'basic';
@Input() badgeStatus: NbComponentOrCustomStatus = 'basic';

/**
* Badge position.
Expand Down
4 changes: 3 additions & 1 deletion src/framework/theme/components/actions/actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterLinkWithHref } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';

import {
NbActionComponent,
NbActionsComponent,
Expand Down Expand Up @@ -206,7 +207,8 @@ describe('NbActionComponent content projection', () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ RouterTestingModule.withRoutes([]), NbActionsModule ], declarations: [ NbActionsTestComponent ],
imports: [ RouterTestingModule.withRoutes([]), NbThemeModule.forRoot(), NbActionsModule ],
declarations: [ NbActionsTestComponent ],
});
const iconLibs: NbIconLibraries = TestBed.inject(NbIconLibraries);
iconLibs.setDefaultPack('nebular-essentials');
Expand Down
29 changes: 15 additions & 14 deletions src/framework/theme/components/alert/alert.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

import { Component, Input, HostBinding, Output, EventEmitter } from '@angular/core';

import { NbStatusService } from '../../services/status.service';
import { NbComponentSize } from '../component-size';
import { NbComponentStatus } from '../component-status';
import { convertToBoolProperty, emptyStatusWarning, NbBooleanInput } from '../helpers';
import { NbComponentOrCustomStatus, NbComponentStatus } from '../component-status';
import { convertToBoolProperty, NbBooleanInput } from '../helpers';


/**
Expand Down Expand Up @@ -130,18 +131,7 @@ export class NbAlertComponent {
* Alert status (adds specific styles):
* `basic` (default), `primary`, `success`, `info`, `warning`, `danger`, `control`.
*/
@Input()
get status(): NbComponentStatus {
return this._status;
}
set status(value: NbComponentStatus) {
if ((value as string) === '') {
emptyStatusWarning('NbAlert');
value = 'basic';
}
this._status = value;
}
protected _status: NbComponentStatus = 'basic';
@Input() status: NbComponentOrCustomStatus = 'basic';

/**
* Alert accent (color of the top border):
Expand Down Expand Up @@ -177,6 +167,9 @@ export class NbAlertComponent {
*/
@Output() close = new EventEmitter();

constructor(protected statusService: NbStatusService) {
}

/**
* Emits the removed chip event
*/
Expand Down Expand Up @@ -313,4 +306,12 @@ export class NbAlertComponent {
get controlOutline() {
return this.outline === 'control';
}

@HostBinding('class')
get additionalClasses(): string[] {
if (this.statusService.isCustomStatus(this.status)) {
return [this.statusService.getStatusClass(this.status)];
}
return [];
}
}
5 changes: 3 additions & 2 deletions src/framework/theme/components/alert/alert.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
*/

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NbAlertComponent } from './alert.component';

import { NbAlertComponent, NbAlertModule, NbThemeModule } from '@nebular/theme';

describe('Component: NbAlert', () => {

Expand All @@ -14,7 +15,7 @@ describe('Component: NbAlert', () => {

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [NbAlertComponent],
imports: [ NbThemeModule.forRoot(), NbAlertModule ],
});

fixture = TestBed.createComponent(NbAlertComponent);
Expand Down
5 changes: 3 additions & 2 deletions src/framework/theme/components/badge/badge.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NbBadgeComponent, NbBadgeModule, NbBadgePosition, NbComponentStatus } from '@nebular/theme';

import { NbBadgeComponent, NbBadgeModule, NbBadgePosition, NbComponentStatus, NbThemeModule } from '@nebular/theme';

describe('NbBadgeComponent', () => {
let fixture: ComponentFixture<NbBadgeComponent>;
let badgeComponent: NbBadgeComponent;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ NbBadgeModule ],
imports: [ NbThemeModule.forRoot(), NbBadgeModule ],
});

fixture = TestBed.createComponent(NbBadgeComponent);
Expand Down
27 changes: 14 additions & 13 deletions src/framework/theme/components/badge/badge.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

import { Component, HostBinding, Input } from '@angular/core';

import { NbComponentStatus } from '../component-status';
import { convertToBoolProperty, emptyStatusWarning } from '../helpers';
import { NbStatusService } from '../../services/status.service';
import { NbComponentOrCustomStatus } from '../component-status';
import { convertToBoolProperty } from '../helpers';

export type NbBadgePhysicalPosition = 'top left' | 'top right' | 'bottom left' | 'bottom right' | 'center right' | 'center left';
export type NbBadgeLogicalPosition = 'top start' | 'top end' | 'bottom start' | 'bottom end' | 'center start'| 'center end';
Expand All @@ -16,7 +17,7 @@ export type NbBadgePosition = NbBadgePhysicalPosition | NbBadgeLogicalPosition;
export interface NbBadge {
text?: string;
position?: NbBadgePosition;
status?: NbComponentStatus;
status?: NbComponentOrCustomStatus;
dotMode?: boolean;
}

Expand Down Expand Up @@ -131,18 +132,15 @@ export class NbBadgeComponent implements NbBadge {
* Badge status (adds specific styles):
* 'basic', 'primary', 'info', 'success', 'warning', 'danger', 'control'
*/
@Input()
get status(): NbComponentStatus {
return this._status;
}
set status(value: NbComponentStatus) {
if ((value as string) === '') {
emptyStatusWarning('NbBadge');
value = 'basic';
@Input() status: NbComponentOrCustomStatus = 'basic';

@HostBinding('class')
get additionalClasses(): string[] {
if (this.statusService.isCustomStatus(this.status)) {
return [this.statusService.getStatusClass(this.status)];
}
this._status = value;
return [];
}
protected _status: NbComponentStatus = 'basic';

@HostBinding('class.status-primary')
get primary(): boolean {
Expand Down Expand Up @@ -213,4 +211,7 @@ export class NbBadgeComponent implements NbBadge {
get center(): boolean {
return this.position.includes('center');
}

constructor(protected statusService: NbStatusService) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@
}

&.appearance-filled {
.status-basic {
color: nb-theme(button-group-filled-button-basic-text-color);
}

@each $status in nb-get-statuses() {
// I can't figure out any sane selector to turn the start border into a divider for buttons
// in the default state only (not hovered, focused, etc.). So I went with this horrible thing.
Expand All @@ -56,6 +52,10 @@
@include nb-ltr(border-left-color, nb-theme(button-group-filled-#{$status}-divider-color));
@include nb-rtl(border-right-color, nb-theme(button-group-filled-#{$status}-divider-color));
}

&.status-#{$status} {
color: nb-theme(button-group-filled-button-#{$status}-text-color);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ import {
import { from, merge, Observable, Subject } from 'rxjs';
import { filter, switchMap, takeUntil } from 'rxjs/operators';

import { NbStatusService } from '../../services/status.service';
import { convertToBoolProperty, NbBooleanInput } from '../helpers';
import { NbComponentSize } from '../component-size';
import { NbComponentShape } from '../component-shape';
import { NbComponentStatus } from '../component-status';
import { NbComponentOrCustomStatus } from '../component-status';
import { NbButton } from '../button/base-button';
import { NbButtonToggleAppearance, NbButtonToggleChange, NbButtonToggleDirective } from './button-toggle.directive';

Expand Down Expand Up @@ -75,6 +76,12 @@ import { NbButtonToggleAppearance, NbButtonToggleChange, NbButtonToggleDirective
* @styles
*
* button-group-filled-button-basic-text-color:
* button-group-filled-button-primary-text-color:
* button-group-filled-button-success-text-color:
* button-group-filled-button-info-text-color:
* button-group-filled-button-warning-text-color:
* button-group-filled-button-danger-text-color:
* button-group-filled-button-control-text-color:
* button-group-filled-basic-divider-color:
* button-group-filled-primary-divider-color:
* button-group-filled-success-divider-color:
Expand Down Expand Up @@ -108,7 +115,7 @@ export class NbButtonGroupComponent implements OnChanges, AfterContentInit {
* Button group status (adds specific styles):
* `basic`, `primary`, `info`, `success`, `warning`, `danger`, `control`
*/
@Input() status: NbComponentStatus = 'basic';
@Input() status: NbComponentOrCustomStatus = 'basic';

/**
* Button group shapes: `rectangle`, `round`, `semi-round`
Expand Down Expand Up @@ -189,7 +196,18 @@ export class NbButtonGroupComponent implements OnChanges, AfterContentInit {

@HostBinding('attr.role') role = 'group';

constructor(protected cd: ChangeDetectorRef) {}
@HostBinding('class')
get additionalClasses(): string[] {
if (this.statusService.isCustomStatus(this.status)) {
return [this.statusService.getStatusClass(this.status)];
}
return [];
}

constructor(
protected cd: ChangeDetectorRef,
protected statusService: NbStatusService,
) {}

ngOnChanges({ size, status, shape, multiple, filled, outline, ghost, disabled }: SimpleChanges) {
if (size || status || shape || multiple || filled || outline || ghost || disabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
NbComponentShape,
NbComponentStatus,
NbButtonToggleAppearance,
NbThemeModule,
} from '@nebular/theme';

@Component({
Expand Down Expand Up @@ -56,8 +57,8 @@ describe('Component: NbButtonGroup', () => {

beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [ NbThemeModule.forRoot(), NbButtonGroupModule, NbButtonModule ],
declarations: [NbButtonGroupTestComponent],
imports: [NbButtonGroupModule, NbButtonModule],
});

fixture = TestBed.createComponent(NbButtonGroupTestComponent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '@angular/core';
import { Observable, Subject } from 'rxjs';

import { NbStatusService } from '../../services/status.service';
import { convertToBoolProperty, NbBooleanInput } from '../helpers';
import { NbButton, NbButtonAppearance } from '../button/base-button';

Expand Down Expand Up @@ -107,6 +108,14 @@ export class NbButtonToggleDirective extends NbButton {
return this.pressed && this.status === 'control';
}

@HostBinding('class')
get additionalClasses(): string[] {
if (this.statusService.isCustomStatus(this.status)) {
return [this.statusService.getStatusClass(this.status)];
}
return [];
}

@HostListener('click')
onClick(): void {
this.pressed = !this.pressed;
Expand All @@ -117,8 +126,9 @@ export class NbButtonToggleDirective extends NbButton {
protected hostElement: ElementRef<HTMLElement>,
protected cd: ChangeDetectorRef,
protected zone: NgZone,
protected statusService: NbStatusService,
) {
super(renderer, hostElement, cd, zone);
super(renderer, hostElement, cd, zone, statusService);
}

/**
Expand Down
17 changes: 14 additions & 3 deletions src/framework/theme/components/button/base-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {
NgZone,
Renderer2,
} from '@angular/core';

import { NbStatusService } from '../../services/status.service';
import { convertToBoolProperty, firstChildNotComment, lastChildNotComment, NbBooleanInput } from '../helpers';
import { NbComponentSize } from '../component-size';
import { NbComponentStatus } from '../component-status';
import { NbComponentOrCustomStatus } from '../component-status';
import { NbComponentShape } from '../component-shape';
import { convertToBoolProperty, firstChildNotComment, lastChildNotComment, NbBooleanInput } from '../helpers';

export type NbButtonAppearance = 'filled' | 'outline' | 'ghost' | 'hero';

Expand All @@ -30,7 +32,7 @@ export abstract class NbButton implements AfterViewInit {
* Button status (adds specific styles):
* `primary`, `info`, `success`, `warning`, `danger`
*/
@Input() status: NbComponentStatus = 'basic';
@Input() status: NbComponentOrCustomStatus = 'basic';

/**
* Button shapes: `rectangle`, `round`, `semi-round`
Expand Down Expand Up @@ -192,11 +194,20 @@ export abstract class NbButton implements AfterViewInit {
return !!(icon && lastChildNotComment(el) === icon);
}

@HostBinding('class')
get additionalClasses(): string[] {
if (this.statusService.isCustomStatus(this.status)) {
return [this.statusService.getStatusClass(this.status)];
}
return [];
}

protected constructor(
protected renderer: Renderer2,
protected hostElement: ElementRef<HTMLElement>,
protected cd: ChangeDetectorRef,
protected zone: NgZone,
protected statusService: NbStatusService,
) {
}

Expand Down
6 changes: 4 additions & 2 deletions src/framework/theme/components/button/button.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
NgZone,
} from '@angular/core';

import { convertToBoolProperty, firstChildNotComment, lastChildNotComment, NbBooleanInput } from '../helpers';
import { NbStatusService } from '../../services/status.service';
import { convertToBoolProperty, NbBooleanInput } from '../helpers';
import { NbButton } from './base-button';

/**
Expand Down Expand Up @@ -608,7 +609,8 @@ export class NbButtonComponent extends NbButton implements AfterViewInit {
protected hostElement: ElementRef<HTMLElement>,
protected cd: ChangeDetectorRef,
protected zone: NgZone,
protected statusService: NbStatusService,
) {
super(renderer, hostElement, cd, zone);
super(renderer, hostElement, cd, zone, statusService);
}
}
Loading

0 comments on commit 3b2e903

Please sign in to comment.