diff --git a/docs/articles/usability/rtl.md b/docs/articles/usability/rtl.md index 0716a81e46..98b5c11e34 100644 --- a/docs/articles/usability/rtl.md +++ b/docs/articles/usability/rtl.md @@ -18,6 +18,7 @@ Document direction could be set through the `NbThemeModule.forRoot` call. Suppor ] }) ``` + Default value is `ltr`.
@@ -28,23 +29,34 @@ Default value is `ltr`.
To help you add RTL support to your custom components, Nebular provides you with two scss mixins: `nb-lrt` and `nb-rtl`. You can use them to alter values of css properties, which don't support logical values, like paddings, margins, etc. You can pass single property and value as arguments, pass multiple statements as the content of mixin or both. For example: + ```scss :host { @include nb-ltr(padding-left, 1em); @include nb-rtl(padding-right, 1em); } ``` + ```scss :host { @include nb-ltr() { padding-left: 1em; - }; + } @include nb-rtl() { padding-right: 1em; - }; + } } ``` Please note, the mixins are only available within component `:host` selector or `nb-install-component()` mixin if used. If you need to change direction dynamically, get current value or listen to changes of direction, Nebular provides `NbLayoutDirectionService`. + +# Layout direction directives + +Nebular provides `nbLtr` and `nbRlt` directives to show content based on the current direction. +For example, you could apply the `nbRtl` directive to the element you need to show only when layout direction is right-to-left add: + +```html +
This text is visible only when layout direction is RTL
+``` diff --git a/docs/structure.ts b/docs/structure.ts index f1bcdd26db..90840843eb 100644 --- a/docs/structure.ts +++ b/docs/structure.ts @@ -351,47 +351,33 @@ export const structure = [ 'NbLayoutHeaderComponent', 'NbLayoutColumnComponent', 'NbLayoutFooterComponent', + 'NbLtrDirective', + 'NbRtlDirective', ], }, { type: 'tabs', name: 'Card', icon: 'card.svg', - source: [ - 'NbCardComponent', - 'NbCardHeaderComponent', - 'NbCardBodyComponent', - 'NbCardFooterComponent', - ], + source: ['NbCardComponent', 'NbCardHeaderComponent', 'NbCardBodyComponent', 'NbCardFooterComponent'], }, { type: 'tabs', name: 'Flip Card', icon: 'flip.svg', - source: [ - 'NbFlipCardComponent', - 'NbCardFrontComponent', - 'NbCardBackComponent', - ], + source: ['NbFlipCardComponent', 'NbCardFrontComponent', 'NbCardBackComponent'], }, { type: 'tabs', name: 'Reveal Card', icon: 'reveal.svg', - source: [ - 'NbRevealCardComponent', - 'NbCardFrontComponent', - 'NbCardBackComponent', - ], + source: ['NbRevealCardComponent', 'NbCardFrontComponent', 'NbCardBackComponent'], }, { type: 'tabs', name: 'Stepper', icon: 'stepper.svg', - source: [ - 'NbStepperComponent', - 'NbStepComponent', - ], + source: ['NbStepperComponent', 'NbStepComponent'], }, { type: 'tabs', @@ -408,13 +394,13 @@ export const structure = [ type: 'tabs', name: 'List', icon: 'list.svg', - source: [ 'NbListComponent', 'NbListItemComponent' ], + source: ['NbListComponent', 'NbListItemComponent'], }, { type: 'tabs', name: 'Infinite List', icon: 'infinite-scroll.svg', - source: [ 'NbInfiniteListDirective', 'NbListPageTrackerDirective' ], + source: ['NbInfiniteListDirective', 'NbListPageTrackerDirective'], }, { type: 'group', @@ -424,41 +410,25 @@ export const structure = [ type: 'tabs', name: 'Sidebar', icon: 'sidebar.svg', - source: [ - 'NbSidebarComponent', - 'NbSidebarHeaderComponent', - 'NbSidebarFooterComponent', - 'NbSidebarService', - ], + source: ['NbSidebarComponent', 'NbSidebarHeaderComponent', 'NbSidebarFooterComponent', 'NbSidebarService'], }, { type: 'tabs', name: 'Menu', icon: 'menu.svg', - source: [ - 'NbMenuComponent', - 'NbMenuItem', - 'NbMenuService', - ], + source: ['NbMenuComponent', 'NbMenuItem', 'NbMenuService'], }, { type: 'tabs', name: 'Tabs', icon: 'tab.svg', - source: [ - 'NbTabsetComponent', - 'NbTabComponent', - 'NbRouteTabsetComponent', - ], + source: ['NbTabsetComponent', 'NbTabComponent', 'NbRouteTabsetComponent'], }, { type: 'tabs', name: 'Actions', icon: 'actions.svg', - source: [ - 'NbActionsComponent', - 'NbActionComponent', - ], + source: ['NbActionsComponent', 'NbActionComponent'], }, { type: 'group', @@ -468,59 +438,43 @@ export const structure = [ type: 'tabs', name: 'Input', icon: 'input.svg', - source: [ 'NbInputDirective' ], + source: ['NbInputDirective'], }, { type: 'tabs', name: 'Button', icon: 'button.svg', - source: [ - 'NbButtonComponent', - ], + source: ['NbButtonComponent'], }, { type: 'tabs', name: 'Button Group', icon: 'button-group.svg', - source: [ - 'NbButtonGroupComponent', - 'NbButtonToggleDirective', - ], + source: ['NbButtonGroupComponent', 'NbButtonToggleDirective'], }, { type: 'tabs', name: 'Checkbox', icon: 'checkbox.svg', - source: [ - 'NbCheckboxComponent', - ], + source: ['NbCheckboxComponent'], }, { type: 'tabs', name: 'Toggle', icon: 'toggle.svg', - source: [ - 'NbToggleComponent', - ], + source: ['NbToggleComponent'], }, { type: 'tabs', name: 'Radio', icon: 'radio.svg', - source: [ - 'NbRadioComponent', - 'NbRadioGroupComponent', - ], + source: ['NbRadioComponent', 'NbRadioGroupComponent'], }, { type: 'tabs', name: 'Select', icon: 'select.svg', - source: [ - 'NbSelectComponent', - 'NbOptionListComponent', - 'NbOptionGroupComponent', - ], + source: ['NbSelectComponent', 'NbOptionListComponent', 'NbOptionGroupComponent'], }, { type: 'tabs', @@ -548,20 +502,13 @@ export const structure = [ type: 'tabs', name: 'Timepicker', icon: 'timepicker.svg', - source: [ - 'NbTimePickerDirective', - 'NbTimePickerComponent', - ], + source: ['NbTimePickerDirective', 'NbTimePickerComponent'], }, { type: 'tabs', name: 'Tag', icon: '', - source: [ - 'NbTagListComponent', - 'NbTagInputDirective', - 'NbTagComponent', - ], + source: ['NbTagListComponent', 'NbTagInputDirective', 'NbTagComponent'], }, { type: 'group', @@ -571,57 +518,37 @@ export const structure = [ type: 'tabs', name: 'Popover', icon: 'popover.svg', - source: [ - 'NbPopoverDirective', - 'NbPopoverComponent', - ], + source: ['NbPopoverDirective', 'NbPopoverComponent'], }, { type: 'tabs', name: 'Context Menu', icon: 'context-menu.svg', - source: [ - 'NbContextMenuDirective', - ], + source: ['NbContextMenuDirective'], }, { type: 'tabs', name: 'Dialog', icon: 'dialog.svg', - source: [ - 'NbDialogService', - 'NbDialogRef', - 'NbDialogConfig', - ], + source: ['NbDialogService', 'NbDialogRef', 'NbDialogConfig'], }, { type: 'tabs', name: 'Toastr', icon: 'toastr.svg', - source: [ - 'NbToastrService', - 'NbToastComponent', - 'NbToastrConfig', - ], + source: ['NbToastrService', 'NbToastComponent', 'NbToastrConfig'], }, { type: 'tabs', name: 'Tooltip', icon: 'tooltip.svg', - source: [ - 'NbTooltipDirective', - 'NbTooltipComponent', - ], + source: ['NbTooltipDirective', 'NbTooltipComponent'], }, { type: 'tabs', name: 'Window', icon: 'collapsable.svg', - source: [ - 'NbWindowService', - 'NbWindowRef', - 'NbWindowConfig', - ], + source: ['NbWindowService', 'NbWindowRef', 'NbWindowConfig'], }, { type: 'group', @@ -631,87 +558,61 @@ export const structure = [ type: 'tabs', name: 'Global Search', icon: 'search.svg', - source: [ - 'NbSearchComponent', - 'NbSearchService', - ], + source: ['NbSearchComponent', 'NbSearchService'], }, { type: 'tabs', name: 'User (Avatar)', icon: 'user.svg', - source: [ - 'NbUserComponent', - ], + source: ['NbUserComponent'], }, { type: 'tabs', name: 'Alert', icon: 'alert.svg', - source: [ - 'NbAlertComponent', - ], + source: ['NbAlertComponent'], }, { type: 'tabs', name: 'Icon', icon: 'icon.svg', - source: [ - 'NbIconComponent', - 'NbIconLibraries', - ], + source: ['NbIconComponent', 'NbIconLibraries'], }, { type: 'tabs', name: 'Spinner', icon: 'spinner.svg', - source: [ - 'NbSpinnerDirective', - 'NbSpinnerComponent', - ], + source: ['NbSpinnerDirective', 'NbSpinnerComponent'], }, { type: 'tabs', name: 'Progress Bar', icon: 'progress-bar.svg', - source: [ - 'NbProgressBarComponent', - ], + source: ['NbProgressBarComponent'], }, { type: 'tabs', name: 'Badge', icon: 'badge.svg', - source: [ - 'NbBadgeComponent', - ], + source: ['NbBadgeComponent'], }, { type: 'tabs', name: 'Chat UI', icon: 'chat-ui.svg', - source: [ - 'NbChatComponent', - 'NbChatMessageComponent', - 'NbChatFormComponent', - 'NbChatCustomMessageDirective', - ], + source: ['NbChatComponent', 'NbChatMessageComponent', 'NbChatFormComponent', 'NbChatCustomMessageDirective'], }, { type: 'tabs', name: 'Calendar', icon: 'calendar.svg', - source: [ - 'NbCalendarComponent', - ], + source: ['NbCalendarComponent'], }, { type: 'tabs', name: 'Calendar Range', icon: 'calendar.svg', - source: [ - 'NbCalendarRangeComponent', - ], + source: ['NbCalendarRangeComponent'], }, { type: 'group', @@ -721,9 +622,7 @@ export const structure = [ type: 'tabs', name: 'Calendar Kit', icon: 'calendar.svg', - source: [ - 'NbCalendarKitModule', - ], + source: ['NbCalendarKitModule'], }, { type: 'group', diff --git a/src/framework/theme/components/layout/layout-direction.directive.ts b/src/framework/theme/components/layout/layout-direction.directive.ts new file mode 100644 index 0000000000..99506bc708 --- /dev/null +++ b/src/framework/theme/components/layout/layout-direction.directive.ts @@ -0,0 +1,90 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { ChangeDetectorRef, Directive, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from '@angular/core'; +import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { NbLayoutDirection, NbLayoutDirectionService } from '../../services/direction.service'; + +@Directive() +abstract class NbBaseLayoutDirectionDirective implements OnInit, OnDestroy { + protected destroy$ = new Subject(); + + constructor( + protected templateRef: TemplateRef, + protected viewContainer: ViewContainerRef, + protected cd: ChangeDetectorRef, + protected directionService: NbLayoutDirectionService, + protected directionToShow: NbLayoutDirection, + ) {} + + ngOnInit(): void { + this.directionService + .onDirectionChange() + .pipe( + map((layoutDirection: NbLayoutDirection) => layoutDirection === this.directionToShow), + distinctUntilChanged(), + takeUntil(this.destroy$), + ) + .subscribe((shouldShow: boolean) => this.updateView(shouldShow)); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + protected updateView(shouldShow: T): void { + if (shouldShow && !this.viewContainer.length) { + this.viewContainer.createEmbeddedView(this.templateRef); + this.cd.markForCheck(); + } else if (!shouldShow && this.viewContainer.length) { + this.viewContainer.clear(); + } + } +} + +/** + * Apply `nbLtr` directive to the element you need to show only when layout direction is `LTR`. + * + * ```html + *
This text is visible only when layout direction is LTR
+ * ``` + */ +@Directive({ + selector: '[nbLtr]', +}) +export class NbLtrDirective extends NbBaseLayoutDirectionDirective { + constructor( + protected templateRef: TemplateRef, + protected viewContainer: ViewContainerRef, + protected cd: ChangeDetectorRef, + protected directionService: NbLayoutDirectionService, + ) { + super(templateRef, viewContainer, cd, directionService, NbLayoutDirection.LTR); + } +} + +/** + * Apply `nbRtl` directive to the element you need to show only when layout direction is `RTL`. + * + * ```html + *
This text is visible only when layout direction is RTL
+ * ``` + */ +@Directive({ + selector: '[nbRtl]', +}) +export class NbRtlDirective extends NbBaseLayoutDirectionDirective { + constructor( + protected templateRef: TemplateRef, + protected viewContainer: ViewContainerRef, + protected cd: ChangeDetectorRef, + protected directionService: NbLayoutDirectionService, + ) { + super(templateRef, viewContainer, cd, directionService, NbLayoutDirection.RTL); + } +} diff --git a/src/framework/theme/components/layout/layout.module.ts b/src/framework/theme/components/layout/layout.module.ts index 6646ac8e8d..878828e0b6 100644 --- a/src/framework/theme/components/layout/layout.module.ts +++ b/src/framework/theme/components/layout/layout.module.ts @@ -16,6 +16,8 @@ import { import { NbRestoreScrollTopHelper } from './restore-scroll-top.service'; +import { NbLtrDirective, NbRtlDirective } from './layout-direction.directive'; + const NB_LAYOUT_COMPONENTS = [ NbLayoutComponent, NbLayoutColumnComponent, @@ -23,18 +25,12 @@ const NB_LAYOUT_COMPONENTS = [ NbLayoutHeaderComponent, ]; +const NB_LAYOUT_DIRECTIVES = [NbLtrDirective, NbRtlDirective]; + @NgModule({ - imports: [ - NbSharedModule, - ], - declarations: [ - ...NB_LAYOUT_COMPONENTS, - ], - providers: [ - NbRestoreScrollTopHelper, - ], - exports: [ - ...NB_LAYOUT_COMPONENTS, - ], + imports: [NbSharedModule], + declarations: [...NB_LAYOUT_COMPONENTS, ...NB_LAYOUT_DIRECTIVES], + providers: [NbRestoreScrollTopHelper], + exports: [...NB_LAYOUT_COMPONENTS, ...NB_LAYOUT_DIRECTIVES], }) -export class NbLayoutModule { } +export class NbLayoutModule {} diff --git a/src/framework/theme/public_api.ts b/src/framework/theme/public_api.ts index c7565e230b..e7d02dc0e4 100644 --- a/src/framework/theme/public_api.ts +++ b/src/framework/theme/public_api.ts @@ -67,6 +67,7 @@ export * from './components/calendar-kit/model'; export * from './components/calendar-kit/calendar-kit.module'; export * from './components/layout/layout.module'; export * from './components/layout/layout.component'; +export * from './components/layout/layout-direction.directive'; export * from './components/layout/restore-scroll-top.service'; export * from './components/menu/menu.module'; export { NbMenuService, NbMenuItem, NbMenuBag } from './components/menu/menu.service';