From b78b8d3ab90dfc53ed56915222052e005bac0caf Mon Sep 17 00:00:00 2001 From: Dmitry Nehaychik <4dmitr@gmail.com> Date: Tue, 7 Aug 2018 17:18:24 +0300 Subject: [PATCH] feat(tabs): ability to assign an icon (#607) Closes #288 --- e2e/route-tabset.e2e-spec.ts | 34 +++++----- .../_route-tabset.component.theme.scss | 8 +++ .../route-tabset/route-tabset.component.scss | 12 ++++ .../route-tabset/route-tabset.component.ts | 13 +++- .../tabset/_tabset.component.theme.scss | 8 +++ .../components/tabset/tabset.component.scss | 67 +++++++++++-------- .../components/tabset/tabset.component.ts | 40 ++++++++++- .../theme/styles/themes/_default.scss | 2 + src/playground/playground-routing.module.ts | 20 +++--- src/playground/playground.module.ts | 16 +++-- .../tabset/route-tabset-showcase.component.ts | 63 +++++++++++++++++ .../tabset/route-tabset-test.component.ts | 41 ------------ .../tabset/tabset-icon.component.html | 54 +++++++++++++++ .../tabset/tabset-icon.component.ts | 20 ++++++ 14 files changed, 293 insertions(+), 105 deletions(-) create mode 100644 src/playground/tabset/route-tabset-showcase.component.ts delete mode 100644 src/playground/tabset/route-tabset-test.component.ts create mode 100644 src/playground/tabset/tabset-icon.component.html create mode 100644 src/playground/tabset/tabset-icon.component.ts diff --git a/e2e/route-tabset.e2e-spec.ts b/e2e/route-tabset.e2e-spec.ts index dbe77be011..441d0e0860 100644 --- a/e2e/route-tabset.e2e-spec.ts +++ b/e2e/route-tabset.e2e-spec.ts @@ -10,62 +10,62 @@ import { hasClass } from './e2e-helper'; describe('nb-route-tabset', () => { beforeEach((done) => { - browser.get('#/tabset/route-tabset-test.component').then(() => done()); + browser.get('#/tabset/route-tabset-showcase.component').then(() => done()); }); it('should display default route-tabset', () => { - expect(element(by.css('nb-route-tabset:nth-child(1) > ul > li:nth-child(1)')) - .getText()).toEqual('Tab #1'); + expect(element(by.css('nb-card:nth-child(1) nb-route-tabset > ul > li:nth-child(1)')) + .getText()).toEqual('Users'); - expect(element(by.css('nb-route-tabset:nth-child(1) > ul > li:nth-child(2)')) - .getText()).toEqual('Tab #2'); + expect(element(by.css('nb-card:nth-child(1) nb-route-tabset > ul > li:nth-child(2)')) + .getText()).toEqual('Orders'); }); it('should change tabs of a route-tabset"', () => { - const tab2 = by.css('nb-route-tabset:nth-child(1) > ul > li:nth-child(2)'); + const tab2 = by.css('nb-card:nth-child(1) nb-route-tabset > ul > li:nth-child(2)'); element(tab2).click() .then(() => { expect(hasClass(element(tab2), 'active')).toBeTruthy(); - expect(browser.getCurrentUrl()).toContain('/#/tabset/route-tabset-test.component/tab2'); + expect(browser.getCurrentUrl()).toContain('/#/tabset/route-tabset-showcase.component/tab2'); }); - const tab1 = by.css('nb-route-tabset:nth-child(1) > ul > li:nth-child(1)'); + const tab1 = by.css('nb-card:nth-child(1) nb-route-tabset > ul > li:nth-child(1)'); element(tab1).click() .then(() => { expect(hasClass(element(tab1), 'active')).toBeTruthy(); - expect(browser.getCurrentUrl()).toContain('/#/tabset/route-tabset-test.component/tab1'); + expect(browser.getCurrentUrl()).toContain('/#/tabset/route-tabset-showcase.component/tab1'); }); }); it('should display a full-width route-tabset', () => { - const tab1 = by.css('nb-route-tabset:nth-child(2) > ul > li:nth-child(1)'); + const tab1 = by.css('nb-card:nth-child(2) nb-route-tabset > ul > li:nth-child(1)'); expect(element(tab1) - .getText()).toEqual('Tab #1'); + .getText()).toEqual('Users'); - const tab2 = by.css('nb-route-tabset:nth-child(2) > ul > li:nth-child(2)'); + const tab2 = by.css('nb-card:nth-child(2) nb-route-tabset > ul > li:nth-child(2)'); expect(element(tab2) - .getText()).toEqual('Tab #2'); + .getText()).toEqual('Orders'); }); it('should change tabs of a full-width route-tabset', () => { - const tab2 = by.css('nb-route-tabset:nth-child(2) > ul > li:nth-child(2)'); + const tab2 = by.css('nb-card:nth-child(2) nb-route-tabset > ul > li:nth-child(2)'); element(tab2).click() .then(() => { expect(hasClass(element(tab2), 'active')).toBeTruthy(); - expect(browser.getCurrentUrl()).toContain('/#/tabset/route-tabset-test.component/tab2'); + expect(browser.getCurrentUrl()).toContain('/#/tabset/route-tabset-showcase.component/tab2'); }); - const tab1 = by.css('nb-route-tabset:nth-child(2) > ul > li:nth-child(1)'); + const tab1 = by.css('nb-card:nth-child(2) nb-route-tabset > ul > li:nth-child(1)'); element(tab1).click() .then(() => { expect(hasClass(element(tab1), 'active')).toBeTruthy(); - expect(browser.getCurrentUrl()).toContain('/#/tabset/route-tabset-test.component/tab1'); + expect(browser.getCurrentUrl()).toContain('/#/tabset/route-tabset-showcase.component/tab1'); }); }); }); diff --git a/src/framework/theme/components/route-tabset/_route-tabset.component.theme.scss b/src/framework/theme/components/route-tabset/_route-tabset.component.theme.scss index 347b92eb57..abc84cdf8b 100644 --- a/src/framework/theme/components/route-tabset/_route-tabset.component.theme.scss +++ b/src/framework/theme/components/route-tabset/_route-tabset.component.theme.scss @@ -52,6 +52,14 @@ color: nb-theme(route-tabs-fg-heading); } } + + &.responsive { + @media screen and (max-width: nb-theme(route-tabs-icon-only-max-width)) { + a span { + display: none; + } + } + } } } } diff --git a/src/framework/theme/components/route-tabset/route-tabset.component.scss b/src/framework/theme/components/route-tabset/route-tabset.component.scss index 98ef7a317c..80fce3808a 100644 --- a/src/framework/theme/components/route-tabset/route-tabset.component.scss +++ b/src/framework/theme/components/route-tabset/route-tabset.component.scss @@ -4,6 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ +@import '../../styles/core/mixins'; + ul { display: flex; flex-direction: row; @@ -34,6 +36,16 @@ ul { bottom: -2px; left: 0; } + + i { + font-size: 1.5rem; + vertical-align: middle; + } + + i + span { + @include nb-ltr(margin-left, 0.5rem); + @include nb-rtl(margin-right, 0.5rem); + } } } } diff --git a/src/framework/theme/components/route-tabset/route-tabset.component.ts b/src/framework/theme/components/route-tabset/route-tabset.component.ts index 02f1fef5e6..1967ff854a 100644 --- a/src/framework/theme/components/route-tabset/route-tabset.component.ts +++ b/src/framework/theme/components/route-tabset/route-tabset.component.ts @@ -20,6 +20,8 @@ import { convertToBoolProperty } from '../helpers'; * { * title: 'Route tab #1', * route: '/pages/description', + * icon: 'nb-home', + * responsive: true, // hide title before `route-tabs-icon-only-max-width` value * }, * { * title: 'Route tab #2', @@ -30,6 +32,8 @@ import { convertToBoolProperty } from '../helpers'; * * ``` * + * @stacked-example(Route Tabset, tabset/route-tabset-showcase.component) + * * @styles * * route-tabs-font-family: @@ -43,6 +47,7 @@ import { convertToBoolProperty } from '../helpers'; * route-tabs-fg-heading: * route-tabs-bg: * route-tabs-selected: + * route-tabs-icon-only-max-width: */ @Component({ selector: 'nb-route-tabset', @@ -53,8 +58,12 @@ import { convertToBoolProperty } from '../helpers'; (click)="$event.preventDefault(); selectTab(tab)" routerLink="{{tab.route}}" routerLinkActive="active" - [routerLinkActiveOptions]="{ exact: true }"> - {{tab.title}} + [routerLinkActiveOptions]="{ exact: true }" + [class.responsive]="tab.responsive"> + + + {{ tab.title }} + diff --git a/src/framework/theme/components/tabset/_tabset.component.theme.scss b/src/framework/theme/components/tabset/_tabset.component.theme.scss index 283b4d1bf3..7766cfb556 100644 --- a/src/framework/theme/components/tabset/_tabset.component.theme.scss +++ b/src/framework/theme/components/tabset/_tabset.component.theme.scss @@ -52,6 +52,14 @@ color: nb-theme(tabs-fg-heading); } } + + &.responsive { + @media screen and (max-width: nb-theme(tabs-icon-only-max-width)) { + a span { + display: none; + } + } + } } } diff --git a/src/framework/theme/components/tabset/tabset.component.scss b/src/framework/theme/components/tabset/tabset.component.scss index 11db456a92..7e90502ae7 100644 --- a/src/framework/theme/components/tabset/tabset.component.scss +++ b/src/framework/theme/components/tabset/tabset.component.scss @@ -4,6 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ +@import '../../styles/core/mixins'; + :host { display: block; @@ -21,38 +23,47 @@ display: block; } } -} + ul { + display: flex; + flex-direction: row; + list-style-type: none; + margin: 0; -ul { - display: flex; - flex-direction: row; - list-style-type: none; - margin: 0; + li { + cursor: pointer; + margin-bottom: -1px; + text-align: center; + position: relative; - li { - cursor: pointer; - margin-bottom: -1px; - text-align: center; - position: relative; + &.active a::before { + display: block; + } - &.active a::before { - display: block; - } + a { + display: flex; + position: relative; + text-decoration: none; - a { - position: relative; - text-decoration: none; - display: inline-block; - - &::before { - display: none; - position: absolute; - content: ''; - width: 100%; - height: 6px; - border-radius: 3px; - bottom: -2px; - left: 0; + &::before { + display: none; + position: absolute; + content: ''; + width: 100%; + height: 6px; + border-radius: 3px; + bottom: -2px; + left: 0; + } + + i { + font-size: 1.5rem; + vertical-align: middle; + } + + i + span { + @include nb-ltr(margin-left, 0.5rem); + @include nb-rtl(margin-right, 0.5rem); + } } } } diff --git a/src/framework/theme/components/tabset/tabset.component.ts b/src/framework/theme/components/tabset/tabset.component.ts index d28641249f..1f99674914 100644 --- a/src/framework/theme/components/tabset/tabset.component.ts +++ b/src/framework/theme/components/tabset/tabset.component.ts @@ -41,13 +41,38 @@ import { convertToBoolProperty } from '../helpers'; }) export class NbTabComponent { + /** + * Tab title + * @type {string} + */ @Input() tabTitle: string; + /** + * Tab icon + * @type {string} + */ + @Input() tabIcon: string; + + /** + * Show only icons when width is smaller than `tabs-icon-only-max-width` + * @type {boolean} + */ + @Input() + set responsive(val: boolean) { + this.responsiveValue = convertToBoolProperty(val); + } + + get responsive() { + return this.responsiveValue; + } + @Input() route: string; @HostBinding('class.content-active') activeValue: boolean = false; + responsiveValue: boolean = false; + /** * Specifies active tab * @returns {boolean} @@ -125,6 +150,13 @@ export class NbTabComponent { * and we can set it to full a width of a parent component * @stacked-example(Full Width, tabset/tabset-width.component) * + * `tabIcon` should be used to add an icon to the tab. Icon can also be combined with title. + * `responsive` tab property if set allows you to hide the title on smaller screens + * (`tabs-icon-only-max-width` property) for better responsive behaviour. You can open the following example and make + * your screen smaller - titles will be hidden in the last tabset in the list: + * + * @stacked-example(Icon, tabset/tabset-icon.component) + * * @styles * * tabs-font-family: @@ -142,8 +174,8 @@ export class NbTabComponent { * tabs-fg-heading: * tabs-bg: * tabs-selected: + * tabs-icon-only-max-width * - ``` */ @Component({ selector: 'nb-tabset', @@ -152,8 +184,12 @@ export class NbTabComponent {