diff --git a/docs/assets/images/components/flip-card.gif b/docs/assets/images/components/flip-card.gif new file mode 100644 index 0000000000..6645f3865a Binary files /dev/null and b/docs/assets/images/components/flip-card.gif differ diff --git a/docs/assets/images/components/reveal-card.gif b/docs/assets/images/components/reveal-card.gif new file mode 100644 index 0000000000..ed680630cb Binary files /dev/null and b/docs/assets/images/components/reveal-card.gif differ diff --git a/docs/structure.ts b/docs/structure.ts index 8887aa8d6e..fee54ddad3 100644 --- a/docs/structure.ts +++ b/docs/structure.ts @@ -266,6 +266,48 @@ export const STRUCTURE = [ }, ], }, + { + type: 'page', + name: 'Flip Card', + children: [ + { + type: 'block', + block: 'component', + blockData: 'NbFlipCardComponent', + }, + { + type: 'block', + block: 'component', + blockData: 'NbCardFrontComponent', + }, + { + type: 'block', + block: 'component', + blockData: 'NbCardBackComponent', + }, + ], + }, + { + type: 'page', + name: 'Reveal Card', + children: [ + { + type: 'block', + block: 'component', + blockData: 'NbRevealCardComponent', + }, + { + type: 'block', + block: 'component', + blockData: 'NbCardFrontComponent', + }, + { + type: 'block', + block: 'component', + blockData: 'NbCardBackComponent', + }, + ], + }, { type: 'page', name: 'Search', diff --git a/e2e/card.e2e-spec.ts b/e2e/card.e2e-spec.ts index cf7a6f38f9..6cbb04eb3e 100644 --- a/e2e/card.e2e-spec.ts +++ b/e2e/card.e2e-spec.ts @@ -5,28 +5,7 @@ */ import { browser, element, by } from 'protractor'; -import { hexToRgbA } from './e2e-helper'; - -const heights = { - xxsmall: '96px', - xsmall: '216px', - small: '336px', - medium: '456px', - large: '576px', - xlarge: '696px', - xxlarge: '816px', -}; - -const colors = { - // Make sure that you convert hex to rgba before validation - primary: '#8a7fff', - success: '#40dc7e', - info: '#4ca6ff', - warning: '#ffa100', - danger: '#ff4c6a', - default: '#a4abb3', - disabled: 'rgba(255, 255, 255, 0.4)', -}; +import { colors, sizes } from './cards-shared'; let cards: any[] = []; @@ -34,25 +13,39 @@ function prepareCards() { const result: any[] = []; let elementNumber: number = 1; - for (const colorKey in colors) { - if (colors.hasOwnProperty(colorKey)) { - for (const heightKey in heights) { - if (heights.hasOwnProperty(heightKey)) { - result.push({ - name: heightKey, - height: heights[heightKey], - colorKey, - color: colorKey === 'disabled' ? colors[colorKey] : hexToRgbA(colors[colorKey]), - elementNumber, - }); - elementNumber++; - } - } + for (const { colorKey, color } of colors) { + for (const { sizeKey, height } of sizes) { + result.push({ + size: sizeKey, + height: height, + colorKey, + color, + elementNumber, + }); + elementNumber++; } } + return result; } +function prepareAccentCards(regularCardsOffset) { + function generateAccentCards(accentCardsOffset, colorKey, color) { + return colors.map((c, i) => ({ + name: colorKey, + colorKey, + color, + accentColor: c.color, + accentKey: c.colorKey, + elementNumber: regularCardsOffset + accentCardsOffset + i, + })); + } + + return colors.reduce((accentCards, { colorKey, color }) => { + return accentCards.concat(generateAccentCards(accentCards.length, colorKey, color)); + }, []); +} + describe('nb-card', () => { cards = prepareCards(); @@ -63,11 +56,11 @@ describe('nb-card', () => { cards.forEach(c => { - it(`should display ${c.colorKey} card with ${c.name} size`, () => { + it(`should display ${c.colorKey} card with ${c.size} size`, () => { expect(element(by.css(`nb-card:nth-child(${c.elementNumber}) > nb-card-header`)) .getText()).toEqual('Header'); - if (c.name !== 'xxsmall') { + if (c.size !== 'xxsmall') { expect(element(by.css(`nb-card:nth-child(${c.elementNumber}) > nb-card-body`)) .getText()).toEqual('Body'); @@ -90,4 +83,15 @@ describe('nb-card', () => { }); }); }); + + const accentCards = prepareAccentCards(cards.length); + accentCards.forEach(c => { + it(`should display ${c.colorKey} card with ${c.accentKey} accent`, () => { + element.all(by.css(`nb-card`)) + .get(c.elementNumber) + .getCssValue('border-top-color').then(borderColor => { + expect(borderColor).toEqual(c.accentColor, 'Accent is not correct'); + }); + }); + }); }); diff --git a/e2e/cards-shared.ts b/e2e/cards-shared.ts new file mode 100644 index 0000000000..fa1e929283 --- /dev/null +++ b/e2e/cards-shared.ts @@ -0,0 +1,21 @@ +import { hexToRgbA } from './e2e-helper'; + +export const sizes = [ + { sizeKey: 'xxsmall', height: '96px' }, + { sizeKey: 'xsmall', height: '216px' }, + { sizeKey: 'small', height: '336px' }, + { sizeKey: 'medium', height: '456px' }, + { sizeKey: 'large', height: '576px' }, + { sizeKey: 'xlarge', height: '696px' }, + { sizeKey: 'xxlarge', height: '816px' }, +]; + +export const colors = [ + { colorKey: 'primary', color: hexToRgbA('#8a7fff') }, + { colorKey: 'success', color: hexToRgbA('#40dc7e') }, + { colorKey: 'info', color: hexToRgbA('#4ca6ff') }, + { colorKey: 'warning', color: hexToRgbA('#ffa100') }, + { colorKey: 'danger', color: hexToRgbA('#ff4c6a') }, + { colorKey: 'default', color: hexToRgbA('#a4abb3') }, + { colorKey: 'disabled', color: 'rgba(255, 255, 255, 0.4)' }, +]; diff --git a/e2e/flip-card.e2e-spec.ts b/e2e/flip-card.e2e-spec.ts new file mode 100644 index 0000000000..bc4ba1a3ac --- /dev/null +++ b/e2e/flip-card.e2e-spec.ts @@ -0,0 +1,59 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { browser, element, by, ExpectedConditions as ec } from 'protractor'; +import { hexToRgbA } from './e2e-helper'; +import { sizes } from './cards-shared'; + +const waitTime = 500; + +const cards = sizes.map((size, i) => ({ size, i })); + +describe('nb-flip-card', () => { + beforeEach((done) => { + browser.get('#/card-status').then(() => done()); + }); + + cards.forEach(c => { + describe(`${c.size.sizeKey} flip card`, () => { + function shouldShowFrontCard () { + const flipCard = element.all(by.tagName('nb-flip-card')).get(c.i); + const frontCard = flipCard.all(by.tagName('.front-container')).first(); + const backCard = flipCard.all(by.tagName('.back-container')).first(); + + expect(flipCard.getAttribute('class')).not.toContain('flipped', `flip card shouldn't be flipped`) + expect(frontCard.isDisplayed()).toBe(true, 'front card should be visible'); + } + + it(`should show front card`, shouldShowFrontCard); + + it(`should flip`, () => { + const flipCard = element.all(by.tagName('nb-flip-card')).get(c.i); + const frontCardFlipButton = flipCard.all(by.css('.flip-button')).first(); + + frontCardFlipButton.click().then(() => { + expect(flipCard.getAttribute('class')).toContain('flipped', 'flip card should be flipped'); + }); + }); + + it(`should flip again`, () => { + const flipCard = element.all(by.tagName('nb-flip-card')).get(c.i); + const frontCardFlipButton = flipCard.all(by.css('.front-container .flip-button')).first(); + const backCardFlipButton = flipCard.all(by.css('.back-container .flip-button')).first(); + + frontCardFlipButton.click() + .then(() => { + browser.sleep(waitTime); + return backCardFlipButton.click() + }) + .then(() => { + browser.sleep(waitTime); + shouldShowFrontCard(); + }); + }); + }); + }); +}); diff --git a/e2e/reveal-card.e2e-spec.ts b/e2e/reveal-card.e2e-spec.ts new file mode 100644 index 0000000000..0e49dd7538 --- /dev/null +++ b/e2e/reveal-card.e2e-spec.ts @@ -0,0 +1,75 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { browser, element, by, ExpectedConditions as ec } from 'protractor'; +import { hexToRgbA } from './e2e-helper'; +import { sizes } from './cards-shared'; +import { protractor } from 'protractor/built/ptor'; + +function toInt(cssValue) { + return parseInt(cssValue, 10); +} + +const waitTime = 1000; + +const cards = sizes.map((size, i) => ({ + size, + i, +})); + +describe('nb-reveal-card', () => { + beforeEach((done) => { + browser.get('#/card-status').then(() => done()); + }); + + cards.forEach(c => { + describe(`${c.size.sizeKey} reveal card`, () => { + function showOnlyFrontCard () { + const revealCard = element.all(by.tagName('nb-reveal-card')).get(c.i); + const frontCard = revealCard.all(by.tagName('nb-card-front')).first(); + const backCardContainer = revealCard.all(by.css('.second-card-container')).first(); + + protractor.promise.all([ + backCardContainer.getCssValue('top'), + revealCard.getCssValue('height'), + revealCard.getCssValue('margin-bottom'), + ]).then(([ backCardTop, cardHeight, cardMargin ]) => { + expect(revealCard.getAttribute('class')).not.toContain('revealed', `card shouldn't has 'revealed' class`); + expect(frontCard.isDisplayed()).toBe(true, 'front card should be visible'); + expect(toInt(backCardTop)).toEqual(toInt(cardHeight) - toInt(cardMargin), 'back card should be hidden'); + }); + } + + it(`should show only front card`, showOnlyFrontCard); + + it(`should reveal back card`, () => { + const revealCard = element.all(by.tagName('nb-reveal-card')).get(c.i); + const backCardContainer = revealCard.all(by.css('.second-card-container')).first(); + const revealButton = revealCard.all(by.css('.reveal-button')).first(); + + revealButton.click().then(() => { + expect(revealCard.getAttribute('class')).toContain('revealed', `card should has 'revealed' class`); + backCardContainer + .getCssValue('top') + .then(top => expect(toInt(top)).toEqual(0, 'revealed card should be visible')); + }); + }); + + it(`should hide back card`, () => { + const revealCard = element.all(by.tagName('nb-reveal-card')).get(c.i); + const backCardContainer = revealCard.all(by.css('.second-card-container')).first(); + const revealButton = revealCard.all(by.css('.reveal-button')).first(); + + revealButton.click() + .then(() => revealButton.click()) + .then(() => { + browser.sleep(waitTime); + showOnlyFrontCard(); + }); + }); + }); + }); +}); diff --git a/src/app/card-test/card-test.component.ts b/src/app/card-test/card-test.component.ts index 9f04c9c886..d878c67ead 100644 --- a/src/app/card-test/card-test.component.ts +++ b/src/app/card-test/card-test.component.ts @@ -11,6 +11,7 @@ import { Component } from '@angular/core'; template: ` + Header @@ -22,6 +23,87 @@ import { Component } from '@angular/core'; Footer + +

Accent Cards

+ + + + Header + + + Body + + + Footer + + + +

Reveal Cards

+ + + + + + Front Header + + + Front Reveal Card Body + + + Front Footer + + + + + + + Back Header + + + Back Reveal Card Body + + + Back Footer + + + + + +

Flip Cards

+ + + + + + Front Header + + + Front Flip Card Body + + + Front Footer + + + + + + + Back Header + + + Back Flip Card Body + + + Back Footer + + + +
`, @@ -30,11 +112,14 @@ export class NbCardTestComponent { sizes = ['xxsmall', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'xxlarge']; statuses = ['primary', 'success', 'info', 'warning', 'danger', 'active', 'disabled']; + accents = ['primary', 'success', 'info', 'warning', 'danger', 'active', 'disabled']; cards: any[]; + enhancedCards: any; constructor() { this.cards = this.prepareCards(); + this.enhancedCards = this.prepareEnhancedCards(); } private prepareCards(): any[] { @@ -51,4 +136,33 @@ export class NbCardTestComponent { return result; } + + private prepareEnhancedCards() { + const { sizes, statuses, accents } = this; + const accentCards = []; + const revealCards = []; + const flipCards = []; + + statuses.forEach(status => { + accents.forEach(accent => { + accentCards.push({ + size: 'small', + status, + accent, + }); + }) + }) + + sizes.forEach(size => { + const card = { size, accent: '', status: '' }; + revealCards.push(card); + flipCards.push(card); + }); + + return { + accentCards, + revealCards, + flipCards, + }; + } } diff --git a/src/framework/theme/components/card/_card.component.theme.scss b/src/framework/theme/components/card/_card.component.theme.scss index b856143b8f..af53cb96fa 100644 --- a/src/framework/theme/components/card/_card.component.theme.scss +++ b/src/framework/theme/components/card/_card.component.theme.scss @@ -101,6 +101,37 @@ border-bottom-color: nb-theme(card-header-danger-bg); } } + + &.accent { + border-top-style: solid; + border-top-width: nb-theme(card-border-radius); + + & nb-card-header { + border-radius: 0; + } + } + + &.accent-active { + border-top-color: nb-theme(card-header-active-bg); + } + &.accent-disabled { + border-top-color: nb-theme(card-header-disabled-bg); + } + &.accent-primary { + border-top-color: nb-theme(card-header-primary-bg); + } + &.accent-success { + border-top-color: nb-theme(card-header-success-bg); + } + &.accent-info { + border-top-color: nb-theme(card-header-info-bg); + } + &.accent-warning { + border-top-color: nb-theme(card-header-warning-bg); + } + &.accent-danger { + border-top-color: nb-theme(card-header-danger-bg); + } } nb-card-body { @@ -126,3 +157,4 @@ @include nb-card-header(); } } + diff --git a/src/framework/theme/components/card/card.component.ts b/src/framework/theme/components/card/card.component.ts index 30755e2f74..d0b0bf069d 100644 --- a/src/framework/theme/components/card/card.component.ts +++ b/src/framework/theme/components/card/card.component.ts @@ -60,6 +60,7 @@ export class NbCardFooterComponent { * * @example While this component can be used alone, it also provides a number * of child components for common card sections: + * * ``` * * @@ -114,8 +115,17 @@ export class NbCardComponent { static readonly STATUS_WARNING = 'warning'; static readonly STATUS_DANGER = 'danger'; + static readonly ACCENT_ACTIVE = 'active'; + static readonly ACCENT_DISABLED = 'disabled'; + static readonly ACCENT_PRIMARY = 'primary'; + static readonly ACCENT_INFO = 'info'; + static readonly ACCENT_SUCCESS = 'success'; + static readonly ACCENT_WARNING = 'warning'; + static readonly ACCENT_DANGER = 'danger'; + size: string; status: string; + accent: string; @HostBinding('class.xxsmall-card') private get xxsmall() { @@ -187,6 +197,46 @@ export class NbCardComponent { return this.status === NbCardComponent.STATUS_DANGER; } + @HostBinding('class.accent') + private get hasAccent() { + return this.accent; + } + + @HostBinding('class.accent-primary') + private get primaryAccent() { + return this.accent === NbCardComponent.ACCENT_PRIMARY; + } + + @HostBinding('class.accent-info') + private get infoAccent() { + return this.accent === NbCardComponent.ACCENT_INFO; + } + + @HostBinding('class.accent-success') + private get successAccent() { + return this.accent === NbCardComponent.ACCENT_SUCCESS; + } + + @HostBinding('class.accent-warning') + private get warningAccent() { + return this.accent === NbCardComponent.ACCENT_WARNING; + } + + @HostBinding('class.accent-danger') + private get dangerAccent() { + return this.accent === NbCardComponent.ACCENT_DANGER; + } + + @HostBinding('class.accent-active') + private get activeAccent() { + return this.accent === NbCardComponent.ACCENT_ACTIVE; + } + + @HostBinding('class.accent-disabled') + private get disabledAccent() { + return this.accent === NbCardComponent.ACCENT_DISABLED; + } + /** * Card size, available sizes: * xxsmall, xsmall, small, medium, large, xlarge, xxlarge @@ -206,4 +256,15 @@ export class NbCardComponent { private set setStatus(val: string) { this.status = val; } + + /** + * Card accent (color of the top border): + * active, disabled, primary, info, success, warning, danger + * @param {string} val + */ + @Input('accent') + private set setAccent(val: string) { + this.accent = val; + } + } diff --git a/src/framework/theme/components/card/card.module.ts b/src/framework/theme/components/card/card.module.ts index 85be67da94..5a1873058f 100644 --- a/src/framework/theme/components/card/card.module.ts +++ b/src/framework/theme/components/card/card.module.ts @@ -15,11 +15,19 @@ import { NbCardHeaderComponent, } from './card.component'; +import { NbRevealCardComponent } from './reveal-card/reveal-card.component'; +import { NbFlipCardComponent } from './flip-card/flip-card.component'; +import { NbCardFrontComponent, NbCardBackComponent } from './shared/shared.component'; + const NB_CARD_COMPONENTS = [ NbCardComponent, NbCardBodyComponent, NbCardFooterComponent, NbCardHeaderComponent, + NbRevealCardComponent, + NbFlipCardComponent, + NbCardFrontComponent, + NbCardBackComponent, ]; @NgModule({ diff --git a/src/framework/theme/components/card/flip-card/_flip-card.component.theme.scss b/src/framework/theme/components/card/flip-card/_flip-card.component.theme.scss new file mode 100644 index 0000000000..6b4503b225 --- /dev/null +++ b/src/framework/theme/components/card/flip-card/_flip-card.component.theme.scss @@ -0,0 +1,7 @@ +@mixin nd-flip-card-theme { + .flip-button { + line-height: nb-theme(card-line-height); + margin-bottom: nb-theme(card-margin); + padding: nb-theme(card-padding); + } +} diff --git a/src/framework/theme/components/card/flip-card/flip-card.component.scss b/src/framework/theme/components/card/flip-card/flip-card.component.scss new file mode 100644 index 0000000000..081856f655 --- /dev/null +++ b/src/framework/theme/components/card/flip-card/flip-card.component.scss @@ -0,0 +1,50 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +:host { + display: block; + perspective: 1200px; + position: relative; + + &.flipped { + .flipcard-body { + transform: rotateY(180deg); + + .front-container .flip-button { + opacity: 0; + } + } + } + + .flipcard-body { + display: flex; + transition: transform 0.5s; + transform-style: preserve-3d; + + .front-container, + .back-container { + backface-visibility: hidden; + flex: 1; + + .flip-button { + cursor: pointer; + position: absolute; + right: 0; + bottom: 0; + opacity: 1; + transition: opacity 0s 0.15s; + } + } + + .front-container { + margin-right: -100%; + } + + .back-container { + transform: rotateY(180deg); + } + } +} diff --git a/src/framework/theme/components/card/flip-card/flip-card.component.ts b/src/framework/theme/components/card/flip-card/flip-card.component.ts new file mode 100644 index 0000000000..71be1424d1 --- /dev/null +++ b/src/framework/theme/components/card/flip-card/flip-card.component.ts @@ -0,0 +1,54 @@ +import { Component, Input, HostBinding } from '@angular/core'; +import { NbCardComponent } from '../card.component' + +/** + * Flip card component. + * + * ![image](assets/images/components/flip-card.gif) + * + * @example + * + * ``` + * + * + * Front Card + * + * + * Back Card + * + * + * ``` + */ +@Component({ + selector: 'nb-flip-card', + styleUrls: ['./flip-card.component.scss'], + template: ` +
+
+ + + + +
+
+ + + + +
+
+ `, +}) +export class NbFlipCardComponent { + /** + * Flip state + * @type boolean + */ + @Input() + @HostBinding('class.flipped') + flipped: boolean = false; + + toggleFlip() { + this.flipped = !this.flipped; + } +} diff --git a/src/framework/theme/components/card/reveal-card/_reveal-card.component.theme.scss b/src/framework/theme/components/card/reveal-card/_reveal-card.component.theme.scss new file mode 100644 index 0000000000..8f5e3b9ad9 --- /dev/null +++ b/src/framework/theme/components/card/reveal-card/_reveal-card.component.theme.scss @@ -0,0 +1,12 @@ +@mixin nd-reveal-card-theme { + .second-card-container { + height: calc(100% - #{nb-theme(card-margin)}); + border-radius: nb-theme(card-border-radius); + } + + .reveal-button { + line-height: nb-theme(card-line-height); + padding: nb-theme(card-padding); + margin-bottom: nb-theme(card-margin); + } +} diff --git a/src/framework/theme/components/card/reveal-card/reveal-card.component.scss b/src/framework/theme/components/card/reveal-card/reveal-card.component.scss new file mode 100644 index 0000000000..8fe248ec65 --- /dev/null +++ b/src/framework/theme/components/card/reveal-card/reveal-card.component.scss @@ -0,0 +1,49 @@ +:host { + display: block; + position: relative; + + &.revealed { + .second-card-container { + top: 0; + transition: none; + + /deep/ nb-card-back { + top: 0; + } + } + .reveal-button { + transform: none; + } + } + + /deep/ nb-card-front { + display: block; + height: 100%; + } + + .second-card-container { + position: absolute; + top: 100%; + right: 0; + left: 0; + overflow: hidden; + transition: top 0s 0.5s; + + /deep/ nb-card-back { + position: absolute; + left: 0; + top: 100%; + width: 100%; + transition: top 0.5s; + } + } + + .reveal-button { + cursor: pointer; + position: absolute; + right: 0; + bottom: 0; + transform: rotate(180deg); + transition: transform 0.3s; + } +} diff --git a/src/framework/theme/components/card/reveal-card/reveal-card.component.ts b/src/framework/theme/components/card/reveal-card/reveal-card.component.ts new file mode 100644 index 0000000000..895eff4aa5 --- /dev/null +++ b/src/framework/theme/components/card/reveal-card/reveal-card.component.ts @@ -0,0 +1,46 @@ +import { Component, Input, HostBinding } from '@angular/core'; + +/** + * Reveal card component. + * + * ![image](assets/images/components/reveal-card.gif) + * + * @example + * + * ``` + * + * + * Front Card + * + * + * Back Card + * + * + * ``` + */ +@Component({ + selector: 'nb-reveal-card', + styleUrls: ['./reveal-card.component.scss'], + template: ` + +
+ +
+ + + + `, +}) +export class NbRevealCardComponent { + /** + * Reveal state + * @type boolean + */ + @Input() + @HostBinding('class.revealed') + revealed: boolean = false; + + toggleReveal() { + this.revealed = !this.revealed; + } +} diff --git a/src/framework/theme/components/card/shared/shared.component.ts b/src/framework/theme/components/card/shared/shared.component.ts new file mode 100644 index 0000000000..510c79f46d --- /dev/null +++ b/src/framework/theme/components/card/shared/shared.component.ts @@ -0,0 +1,24 @@ +import { Component, Input, HostBinding } from '@angular/core'; +import { NbCardComponent } from '../card.component' + +/** + * Component intended to be used within the `` and `` components. + * + * Use it as a container for the front card. + */ +@Component({ + selector: 'nb-card-front', + template: '', +}) +export class NbCardFrontComponent { } + +/** + * Component intended to be used within the `` and `` components. + * + * Use it as a container for the back card. + */ +@Component({ + selector: 'nb-card-back', + template: '', +}) +export class NbCardBackComponent { } diff --git a/src/framework/theme/styles/global/_components.scss b/src/framework/theme/styles/global/_components.scss index 973194ea93..47d99d6252 100644 --- a/src/framework/theme/styles/global/_components.scss +++ b/src/framework/theme/styles/global/_components.scss @@ -7,6 +7,8 @@ @import '../../components/layout/layout.component.theme'; @import '../../components/sidebar/sidebar.component.theme'; @import '../../components/card/card.component.theme'; +@import '../../components/card/flip-card/flip-card.component.theme'; +@import '../../components/card/reveal-card/reveal-card.component.theme'; @import '../../components/tabset/tabset.component.theme'; @import '../../components/route-tabset/route-tabset.component.theme'; @import '../../components/menu/menu.component.theme'; @@ -21,6 +23,8 @@ @include nb-layout-theme(); @include nb-sidebar-theme(); @include nb-card-theme(); + @include nd-reveal-card-theme(); + @include nd-flip-card-theme(); @include nb-tabset-theme(); @include nb-route-tabset-theme(); @include nb-menu-theme();