From 6399fabb87d7283dfd24e28ae3a9f6d293062718 Mon Sep 17 00:00:00 2001 From: Sergey Andrievskiy Date: Mon, 22 Apr 2019 12:15:40 +0200 Subject: [PATCH] feat(actions): Eva style (#1397) BREAKING CHANGE: NbActionsComponent 'inverseValue' field and 'inverse' setter removed. NbActionsComponent 'fullWidthValue' field replaced with 'fullWidth'. NbActionsComponent size class named renamed to 'size-[size-name]'. NbActionsComponent static fields SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE removed. NbActionComponent 'disabledValue' field replaced with 'disabled'. Following theme variables name were renamed: actions-font-size -> actions-[size]-text-font-size actions-font-family -> actions-text-font-family actions-line-height -> actions-text-line-height actions-fg -> actions-text-color actions-bg -> actions-background-color actions-separator -> actions-divider-color actions-padding -> actions-[size]-padding actions-size-small -> actions-small-height actions-size-medium -> actions-medium-height actions-size-large -> actions-large-height --- .../actions/_actions.component.theme.scss | 88 ++---- .../components/actions/action.component.scss | 36 +++ .../components/actions/actions.component.scss | 23 -- .../components/actions/actions.component.ts | 127 ++++---- .../theme/components/actions/actions.spec.ts | 281 ++++++++++++++++++ src/framework/theme/index.ts | 1 + .../theme/styles/themes/_default.scss | 43 ++- .../action/action-sizes.component.html | 31 +- .../action/action-sizes.component.ts | 2 + .../with-layout/action/action.module.ts | 2 + 10 files changed, 458 insertions(+), 176 deletions(-) create mode 100644 src/framework/theme/components/actions/action.component.scss create mode 100644 src/framework/theme/components/actions/actions.spec.ts diff --git a/src/framework/theme/components/actions/_actions.component.theme.scss b/src/framework/theme/components/actions/_actions.component.theme.scss index ce441a046e..6ab78171c1 100644 --- a/src/framework/theme/components/actions/_actions.component.theme.scss +++ b/src/framework/theme/components/actions/_actions.component.theme.scss @@ -6,77 +6,45 @@ @mixin nb-actions-theme() { nb-actions { + background-color: nb-theme(actions-background-color); + color: nb-theme(actions-text-color); + font-family: nb-theme(actions-text-font-family); + font-weight: nb-theme(actions-text-font-weight); + line-height: nb-theme(actions-text-line-height); + } - font-size: nb-theme(actions-font-size); - font-family: nb-theme(actions-font-family); - line-height: nb-theme(actions-line-height); - - nb-action { - height: nb-theme(actions-size-small); - padding: 0 nb-theme(actions-padding); - - &:first-child { - @include nb-ltr(border-left, none!important); - @include nb-rtl(border-right, none!important); - } - - a.icon-container { - &:hover, &:focus { - text-decoration: none; - } - } + @each $size in nb-get-sizes() { + nb-actions.size-#{$size} nb-action { + font-size: nb-theme(actions-#{$size}-text-font-size); + height: nb-theme(actions-#{$size}-height); + padding: nb-theme(actions-#{$size}-padding); nb-icon { - color: nb-theme(actions-fg); - font-size: nb-theme(actions-size-small); + font-size: nb-theme(actions-#{$size}-icon-height); } - - @include nb-ltr(border-left, 1px solid nb-theme(actions-separator)); - @include nb-rtl(border-right, 1px solid nb-theme(actions-separator)); - - background: transparent; } + } - &.inverse { - nb-action { - nb-icon { - color: nb-theme(actions-bg); - } + nb-action { + $divider: nb-theme(actions-divider-width) nb-theme(actions-divider-style) nb-theme(actions-divider-color); + @include nb-ltr(border-left, $divider); + @include nb-rtl(border-right, $divider); - @include nb-ltr(border-left, 1px solid nb-theme(actions-separator)); - @include nb-rtl(border-right, 1px solid nb-theme(actions-separator)); - } + &:first-child { + @include nb-ltr(border-left, none!important); + @include nb-rtl(border-right, none!important); } - &.small { - nb-action { - height: nb-theme(actions-size-small); - nb-icon { - font-size: nb-theme(actions-size-small); - } - } - } - &.medium { - nb-action { - height: nb-theme(actions-size-medium); - nb-icon { - font-size: nb-theme(actions-size-medium); - } - } - } - &.large { - nb-action { - height: nb-theme(actions-size-large); - nb-icon { - font-size: nb-theme(actions-size-large); - } - } + nb-icon { + color: nb-theme(actions-icon-color); } - &.full-width nb-action { - display: flex; - justify-content: center; - width: 100%; + &.disabled { + color: nb-theme(actions-disabled-text-color); + + nb-icon { + color: nb-theme(actions-disabled-icon-color); + } } } } diff --git a/src/framework/theme/components/actions/action.component.scss b/src/framework/theme/components/actions/action.component.scss new file mode 100644 index 0000000000..e0270c8d03 --- /dev/null +++ b/src/framework/theme/components/actions/action.component.scss @@ -0,0 +1,36 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +:host { + background: transparent; + display: flex; + flex-wrap: wrap; + align-items: center; + position: relative; +} + +:host(.disabled) { + cursor: not-allowed; + + a, nb-icon { + cursor: not-allowed; + } +} + +:host-context(nb-actions.full-width) { + justify-content: center; + width: 100%; +} + +a.icon-container { + &:hover, &:focus { + text-decoration: none; + } +} + +nb-icon:hover { + cursor: pointer; +} diff --git a/src/framework/theme/components/actions/actions.component.scss b/src/framework/theme/components/actions/actions.component.scss index a4a401a9ba..4c4ee1647e 100644 --- a/src/framework/theme/components/actions/actions.component.scss +++ b/src/framework/theme/components/actions/actions.component.scss @@ -5,29 +5,6 @@ */ :host { - display: flex; align-items: center; - - /deep/ nb-action { - display: flex; - flex-wrap: wrap; - align-items: center; - position: relative; - - nb-icon:hover { - cursor: pointer; - } - - &.disabled { - & > * { - opacity: 0.5; - } - cursor: not-allowed; - - a, i { - cursor: not-allowed!important; - } - } - } } diff --git a/src/framework/theme/components/actions/actions.component.ts b/src/framework/theme/components/actions/actions.component.ts index 174aee57cf..779f634b1d 100644 --- a/src/framework/theme/components/actions/actions.component.ts +++ b/src/framework/theme/components/actions/actions.component.ts @@ -7,12 +7,14 @@ import { Component, HostBinding, Input } from '@angular/core'; import { convertToBoolProperty } from '../helpers'; +import { NbComponentSize } from '../component-size'; /** * Action item, display a link with an icon, or any other content provided instead. */ @Component({ selector: 'nb-action', + styleUrls: ['./action.component.scss'], template: ` + + {{ projectedText }} + + + `, +}) +export class NbActionsTestComponent { + projectContent: boolean = false; + projectedText = 'text.text.text.text.text'; + + icon: string; + link: string; + + @ViewChild(NbActionsComponent) actionsComponent: NbActionsComponent; + @ViewChild(NbActionComponent) actionComponent: NbActionComponent; +} + +describe('NbActionComponent link with icon', () => { + + let fixture: ComponentFixture; + let actionComponent: NbActionComponent; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([]), + NbThemeModule.forRoot(), + NbBadgeModule, + NbActionsModule, + ], + }); + const iconLibs: NbIconLibraries = TestBed.get(NbIconLibraries); + iconLibs.setDefaultPack('nebular-essentials'); + + fixture = TestBed.createComponent(NbActionComponent); + actionComponent = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should contain router link if link input set', () => { + actionComponent.icon = ICON_NAME; + actionComponent.link = '.'; + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.directive(RouterLinkWithHref)); + expect(link).not.toBeNull(); + }); + + it('should contain anchor with href if href input set', () => { + actionComponent.icon = ICON_NAME; + actionComponent.href = '/'; + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.css('a[href]')); + expect(link).not.toBeNull(); + expect(link.properties.href).toEqual('/'); + }); + + it('should not contain router link if href input set', () => { + actionComponent.icon = ICON_NAME; + actionComponent.href = '/'; + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.directive(RouterLinkWithHref)); + expect(link).toBeNull(); + }); + + it('should contain empty link if neither link nor href inputs set', () => { + actionComponent.icon = ICON_NAME; + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.css('a[href]')); + expect(link).not.toBeNull(); + expect(link.attributes.href).toEqual('#'); + }); + + it('should contain icon in routerLink anchor', () => { + actionComponent.icon = ICON_NAME; + actionComponent.link = '.'; + fixture.detectChanges(); + + const icon = fixture.debugElement + .query(By.directive(RouterLinkWithHref)) + .query(By.directive(NbIconComponent)); + + expect(icon).not.toBeNull(); + expect(icon.componentInstance.icon).toEqual(ICON_NAME); + }); + + it('should contain icon in href anchor', () => { + actionComponent.icon = ICON_NAME; + actionComponent.href = '/'; + fixture.detectChanges(); + + const icon = fixture.debugElement + .query(By.css('a[href]')) + .query(By.directive(NbIconComponent)); + + expect(icon).not.toBeNull(); + expect(icon.componentInstance.icon).toEqual(ICON_NAME); + }); + + it('should contain icon in empty anchor', () => { + actionComponent.icon = ICON_NAME; + fixture.detectChanges(); + + const icon = fixture.debugElement + .query(By.css('a')) + .query(By.directive(NbIconComponent)); + + expect(icon).not.toBeNull(); + expect(icon.componentInstance.icon).toEqual(ICON_NAME); + }); + + it('should set title in routerLink anchor', () => { + actionComponent.icon = ICON_NAME; + actionComponent.link = '.'; + const title = 'routerLink'; + actionComponent.title = title; + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.directive(RouterLinkWithHref)); + + expect(link.properties.title).toEqual(title); + }); + + it('should set title in href anchor', () => { + actionComponent.icon = ICON_NAME; + actionComponent.href = '/'; + const title = 'hrefAnchor'; + actionComponent.title = title; + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.css('a')); + + expect(link.properties.title).toEqual(title); + }); + + it('should set title in empty anchor', () => { + actionComponent.icon = ICON_NAME; + const title = 'emptyAnchor'; + actionComponent.title = title; + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.css('a')); + + expect(link.properties.title).toEqual(title); + }); + + it('should set class if disabled', () => { + actionComponent.disabled = true; + fixture.detectChanges(); + + expect(fixture.debugElement.classes.disabled).toEqual(true); + }); + + it('should contain badge if badgeText set', () => { + const badgeText = '1'; + actionComponent.badgeText = badgeText; + fixture.detectChanges(); + + const badge = fixture.debugElement.query(By.directive(NbBadgeComponent)); + expect(badge).not.toBeNull(); + expect(badge.componentInstance.text).toEqual('1'); + }); + + it('should pass set badge position and status to badge component', () => { + actionComponent.badgeText = '1'; + actionComponent.badgePosition = NbBadgeComponent.BOTTOM_RIGHT; + actionComponent.badgeStatus = NbBadgeComponent.STATUS_INFO; + fixture.detectChanges(); + + const badge = fixture.debugElement.query(By.directive(NbBadgeComponent)); + const badgeComponent: NbBadgeComponent = badge.componentInstance; + expect(badgeComponent.position).toEqual(NbBadgeComponent.BOTTOM_RIGHT); + expect(badgeComponent.colorClass).toEqual(NbBadgeComponent.STATUS_INFO); + }); +}); + +describe('NbActionComponent content projection', () => { + + let fixture: ComponentFixture; + let testComponent: NbActionsTestComponent; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ RouterTestingModule.withRoutes([]), NbActionsModule ], declarations: [ NbActionsTestComponent ], + }); + const iconLibs: NbIconLibraries = TestBed.get(NbIconLibraries); + iconLibs.setDefaultPack('nebular-essentials'); + + fixture = TestBed.createComponent(NbActionsTestComponent); + testComponent = fixture.componentInstance; + + fixture.detectChanges(); + }); + + it('should not contain anything if neither icon nor content passed', () => { + const action = fixture.debugElement.query(By.directive(NbActionComponent)); + expect(action.nativeElement.textContent).toEqual(''); + }); + + it('should contain projected content if passed', () => { + testComponent.projectContent = true; + fixture.detectChanges(); + + const action = fixture.debugElement.query(By.directive(NbActionComponent)); + expect(action.nativeElement.textContent).toEqual(testComponent.projectedText); + }); + + it('should not render projected content if icon input set', () => { + testComponent.icon = ICON_NAME; + testComponent.link = '/'; + testComponent.projectContent = true; + fixture.detectChanges(); + + const action = fixture.debugElement.query(By.directive(NbActionComponent)); + const link = action.query(By.css('a')); + const icon = action.query(By.directive(NbIconComponent)); + expect(link).not.toBeNull(); + expect(icon).not.toBeNull(); + expect(action.nativeElement.textContent).toEqual(''); + }); +}); + +describe('NbActionsComponent', () => { + + let fixture: ComponentFixture; + let actionsComponent: NbActionsComponent; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ NbActionsModule ], + }); + + fixture = TestBed.createComponent(NbActionsComponent); + actionsComponent = fixture.componentInstance; + + fixture.detectChanges(); + }); + + it('should has full width class if fullWidth input set', () => { + actionsComponent.fullWidth = true; + fixture.detectChanges(); + + expect(fixture.debugElement.classes['full-width']).toEqual(true); + }); + + it('should set size class corresponding to current size', () => { + const sizes: NbComponentSize[] = [ 'tiny', 'small', 'medium', 'large', 'giant' ]; + + for (const size of sizes) { + actionsComponent.size = size; + fixture.detectChanges(); + + expect(fixture.debugElement.classes[`size-${size}`]).toEqual(true); + } + }); +}); diff --git a/src/framework/theme/index.ts b/src/framework/theme/index.ts index 942894e42d..b2ec91f0ab 100644 --- a/src/framework/theme/index.ts +++ b/src/framework/theme/index.ts @@ -38,6 +38,7 @@ export * from './components/tabset/tabset.component'; export * from './components/user/user.module'; export * from './components/user/user.component'; export * from './components/actions/actions.module'; +export * from './components/actions/actions.component'; export * from './components/search/search.module'; export * from './components/search/search.service'; export * from './components/search/search.component'; diff --git a/src/framework/theme/styles/themes/_default.scss b/src/framework/theme/styles/themes/_default.scss index 06fd0d02cf..117e3756ab 100644 --- a/src/framework/theme/styles/themes/_default.scss +++ b/src/framework/theme/styles/themes/_default.scss @@ -566,16 +566,39 @@ $theme: ( context-menu-shadow: none, context-menu-arrow-size: 11px, - actions-font-size: font-size, - actions-font-family: font-secondary, - actions-line-height: line-height, - actions-fg: color-fg, - actions-bg: color-bg, - actions-separator: separator, - actions-padding: padding, - actions-size-small: 1.5rem, - actions-size-medium: 2.25rem, - actions-size-large: 3.5rem, + actions-background-color: transparent, + actions-divider-color: divider-color, + actions-divider-style: solid, + actions-divider-width: 0.0625rem, + actions-icon-color: text-hint-color, + actions-text-color: text-dark-color, + actions-text-font-family: text-button-font-family, + actions-text-font-weight: text-button-font-weight, + actions-text-line-height: text-button-line-height, + + actions-disabled-icon-color: text-disabled-color, + actions-disabled-text-color: text-disabled-color, + + actions-tiny-height: 1rem, + actions-tiny-icon-height: actions-tiny-height, + actions-tiny-padding: 0 1.25rem, + actions-tiny-text-font-size: text-button-tiny-font-size, + actions-small-height: 1.5rem, + actions-small-icon-height: actions-small-height, + actions-small-padding: 0 1.25rem, + actions-small-text-font-size: text-button-small-font-size, + actions-medium-height: 2.25rem, + actions-medium-icon-height: actions-medium-height, + actions-medium-padding: 0 1.25rem, + actions-medium-text-font-size: text-button-medium-font-size, + actions-large-height: 3.5rem, + actions-large-icon-height: actions-large-height, + actions-large-padding: 0 1.25rem, + actions-large-text-font-size: text-button-large-font-size, + actions-giant-height: 4rem, + actions-giant-icon-height: actions-giant-height, + actions-giant-padding: 0 1.25rem, + actions-giant-text-font-size: text-button-giant-font-size, search-btn-open-fg: color-fg, search-btn-close-fg: color-fg, diff --git a/src/playground/with-layout/action/action-sizes.component.html b/src/playground/with-layout/action/action-sizes.component.html index f010bd24f6..b7c886f5cb 100644 --- a/src/playground/with-layout/action/action-sizes.component.html +++ b/src/playground/with-layout/action/action-sizes.component.html @@ -1,37 +1,12 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + Some Action diff --git a/src/playground/with-layout/action/action-sizes.component.ts b/src/playground/with-layout/action/action-sizes.component.ts index 3a0eb94519..93bd8a3564 100644 --- a/src/playground/with-layout/action/action-sizes.component.ts +++ b/src/playground/with-layout/action/action-sizes.component.ts @@ -5,6 +5,7 @@ */ import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { NbComponentSize } from '@nebular/theme'; @Component({ selector: 'nb-action-sizes', @@ -12,4 +13,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; templateUrl: './action-sizes.component.html', }) export class ActionSizesComponent { + sizes: NbComponentSize[] = [ 'tiny', 'small', 'medium', 'large', 'giant' ]; } diff --git a/src/playground/with-layout/action/action.module.ts b/src/playground/with-layout/action/action.module.ts index 114b6b7b1e..bfdf1ed9c9 100644 --- a/src/playground/with-layout/action/action.module.ts +++ b/src/playground/with-layout/action/action.module.ts @@ -4,6 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ +import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NbActionsModule, NbCardModule, NbLayoutModule, NbUserModule } from '@nebular/theme'; import { ActionRoutingModule } from './action-routing.module'; @@ -27,6 +28,7 @@ import { ActionWidthComponent } from './action-width.component'; NbCardModule, NbUserModule, ActionRoutingModule, + CommonModule, ], }) export class ActionModule {}