Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(admin-ui): Add rtl compatibility to some admin-ui components #2451

Merged
merged 6 commits into from
Oct 17, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import { I18nService } from '../../providers/i18n/i18n.service';
import { LocalStorageService } from '../../providers/local-storage/local-storage.service';
import { ModalService } from '../../providers/modal/modal.service';
import { UiLanguageSwitcherDialogComponent } from '../ui-language-switcher-dialog/ui-language-switcher-dialog.component';
import {
LocalizationDirectionType,
LocalizationLanguageCodeType,
LocalizationService,
} from '../../providers/localization/localization.service';

@Component({
selector: 'vdr-app-shell',
Expand All @@ -22,8 +27,8 @@ import { UiLanguageSwitcherDialogComponent } from '../ui-language-switcher-dialo
export class AppShellComponent implements OnInit {
version = ADMIN_UI_VERSION;
userName$: Observable<string>;
uiLanguageAndLocale$: Observable<[LanguageCode, string | undefined]>;
direction$: Observable<'ltr' | 'rtl'>;
uiLanguageAndLocale$: LocalizationLanguageCodeType;
direction$: LocalizationDirectionType;
availableLanguages: LanguageCode[] = [];
hideVendureBranding = getAppConfig().hideVendureBranding;
pageTitle$: Observable<string>;
Expand All @@ -38,25 +43,27 @@ export class AppShellComponent implements OnInit {
private modalService: ModalService,
private localStorageService: LocalStorageService,
private breadcrumbService: BreadcrumbService,
private localizationService: LocalizationService,
) {}

ngOnInit() {
this.direction$ = this.localizationService.direction$;

this.uiLanguageAndLocale$ = this.localizationService.uiLanguageAndLocale$;

this.userName$ = this.dataService.client
.userStatus()
.single$.pipe(map(data => data.userStatus.username));
this.uiLanguageAndLocale$ = this.dataService.client
.uiState()
.stream$.pipe(map(({ uiState }) => [uiState.language, uiState.locale ?? undefined]));

this.availableLanguages = this.i18nService.availableLanguages;

this.pageTitle$ = this.breadcrumbService.breadcrumbs$.pipe(
map(breadcrumbs => breadcrumbs[breadcrumbs.length - 1].label),
);

this.mainNavExpanded$ = this.dataService.client
.uiState()
.stream$.pipe(map(({ uiState }) => uiState.mainNavExpanded));
this.direction$ = this.uiLanguageAndLocale$.pipe(
map(([languageCode]) => (this.i18nService.isRTL(languageCode) ? 'rtl' : 'ltr')),
);
}

selectUiLanguage() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
<div
class="notification-wrapper"
#wrapper
[style.top.px]="offsetTop"
[ngClass]="{
visible: isVisible,
info: type === 'info',
success: type === 'success',
error: type === 'error',
warning: type === 'warning'
}"
>
<div [dir]="direction$ | async" class="notification-wrapper" #wrapper [style.top.px]="offsetTop" [ngClass]="{
visible: isVisible,
info: type === 'info',
success: type === 'success',
error: type === 'error',
warning: type === 'warning'
}">
<clr-icon [attr.shape]="getIcon()" size="24"></clr-icon>
{{ stringifyMessage(message) | translate: translationVars }}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { Component, ElementRef, HostListener, ViewChild } from '@angular/core';
import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core';

import { NotificationType } from '../../providers/notification/notification.service';

import {
LocalizationDirectionType,
LocalizationService,
} from '../../providers/localization/localization.service';

@Component({
selector: 'vdr-notification',
templateUrl: './notification.component.html',
styleUrls: ['./notification.component.scss'],
})
export class NotificationComponent {
export class NotificationComponent implements OnInit {
direction$: LocalizationDirectionType;

@ViewChild('wrapper', { static: true }) wrapper: ElementRef;
offsetTop = 0;
message = '';
Expand All @@ -18,6 +25,15 @@ export class NotificationComponent {
/* */
};

/**
*
*/
constructor(private localizationService: LocalizationService) {}

ngOnInit(): void {
this.direction$ = this.localizationService.direction$;
}

registerOnClickFn(fn: () => void): void {
this.onClickFn = fn;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
:host {
display: flex;
justify-content: start;
justify-content: flex-start;
align-items: center;
}

Expand All @@ -27,10 +27,24 @@ button.theme-toggle {
left: 0px;
opacity: 1;
}

&.default.active {
color: #d6ae3f;
}

&.dark.active {
color: #ffdf3a;
}
}

:host-context([dir='rtl']) {
.theme-icon {
left: auto;
right: 6px;

&.active {
left: auto;
right: 0px;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { TestBed } from '@angular/core/testing';

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { TestingCommonModule } from '../../../../../testing/testing-common.module';
import { MockI18nService } from '../i18n/i18n.service.mock';
import { DataService } from '../../data/providers/data.service';
import { I18nService } from '../../providers/i18n/i18n.service';
import { LocalizationService } from './localization.service';

describe('LocalizationService', () => {
let service: LocalizationService;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestingCommonModule],
providers: [
LocalizationService,
{ provide: I18nService, useClass: MockI18nService },
{ provide: DataService, useClass: class {} },
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
});
service = TestBed.inject(LocalizationService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Injectable } from '@angular/core';
import { Observable, map } from 'rxjs';

import { DataService } from '../../data/providers/data.service';
import { I18nService } from '../../providers/i18n/i18n.service';
import { LanguageCode } from '../../common/generated-types';

export type LocalizationDirectionType = Observable<'ltr' | 'rtl'>;
export type LocalizationLanguageCodeType = Observable<[LanguageCode, string | undefined]>;

/**
* @description
* Provides localization helper functionality.
*
*/
@Injectable({
providedIn: 'root',
})
export class LocalizationService {
uiLanguageAndLocale$: LocalizationLanguageCodeType;
direction$: LocalizationDirectionType;

constructor(private i18nService: I18nService, private dataService: DataService) {
this.uiLanguageAndLocale$ = this.dataService.client
?.uiState()
?.stream$?.pipe(map(({ uiState }) => [uiState.language, uiState.locale ?? undefined]));

this.direction$ = this.uiLanguageAndLocale$?.pipe(
map(([languageCode]) => {
return this.i18nService.isRTL(languageCode) ? 'rtl' : 'ltr';
}),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import {
ConnectedPosition,
HorizontalConnectionPos,
Overlay,
OverlayRef,
PositionStrategy,
VerticalConnectionPos,
} from '@angular/cdk/overlay';
import { ConnectedPosition, Overlay, OverlayRef, PositionStrategy } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ContentChild,
ElementRef,
HostListener,
Input,
OnDestroy,
Expand All @@ -23,7 +14,10 @@ import {
} from '@angular/core';
import { Subscription } from 'rxjs';

import { DropdownTriggerDirective } from './dropdown-trigger.directive';
import {
LocalizationDirectionType,
LocalizationService,
} from '../../../providers/localization/localization.service';
import { DropdownComponent } from './dropdown.component';

export type DropdownPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
Expand All @@ -41,14 +35,16 @@ export type DropdownPosition = 'top-left' | 'top-right' | 'bottom-left' | 'botto
selector: 'vdr-dropdown-menu',
template: `
<ng-template #menu>
<div class="dropdown open">
<div class="dropdown-menu" [ngClass]="customClasses">
<div
class="dropdown-content-wrapper"
[cdkTrapFocus]="true"
[cdkTrapFocusAutoCapture]="true"
>
<ng-content></ng-content>
<div [dir]="direction$ | async">
<div class="dropdown open">
<div class="dropdown-menu" [ngClass]="customClasses">
<div
class="dropdown-content-wrapper"
[cdkTrapFocus]="true"
[cdkTrapFocusAutoCapture]="true"
>
<ng-content></ng-content>
</div>
</div>
</div>
</div>
Expand All @@ -58,6 +54,8 @@ export type DropdownPosition = 'top-left' | 'top-right' | 'bottom-left' | 'botto
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DropdownMenuComponent implements AfterViewInit, OnInit, OnDestroy {
direction$: LocalizationDirectionType;

@Input('vdrPosition') private position: DropdownPosition = 'bottom-left';
@Input() customClasses: string;
@ViewChild('menu', { static: true }) private menuTemplate: TemplateRef<any>;
Expand Down Expand Up @@ -104,9 +102,12 @@ export class DropdownMenuComponent implements AfterViewInit, OnInit, OnDestroy {
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private dropdown: DropdownComponent,
private localizationService: LocalizationService,
) {}

ngOnInit(): void {
this.direction$ = this.localizationService.direction$;

this.dropdown.onOpenChange(isOpen => {
if (isOpen) {
this.overlayRef.attach(this.menuPortal);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
<clr-modal
[clrModalOpen]="true"
(clrModalOpenChange)="modalOpenChange($event)"
[clrModalClosable]="options?.closable"
[clrModalSize]="options?.size"
[ngClass]="'modal-valign-' + (options?.verticalAlign || 'center')"
>
<h3 class="modal-title"><ng-container *ngTemplateOutlet="(titleTemplateRef$ | async)"></ng-container></h3>
<div class="modal-body">
<vdr-dialog-component-outlet
[component]="childComponentType"
(create)="onCreate($event)"
></vdr-dialog-component-outlet>
</div>
<div class="modal-footer">
<ng-container *ngTemplateOutlet="(buttonsTemplateRef$ | async)"></ng-container>
</div>
</clr-modal>
<div [dir]="direction$ | async">
<clr-modal [clrModalOpen]="true" (clrModalOpenChange)="modalOpenChange($event)"
[clrModalClosable]="options?.closable" [clrModalSize]="options?.size"
[ngClass]="'modal-valign-' + (options?.verticalAlign || 'center')">
<h3 class="modal-title"><ng-container *ngTemplateOutlet="(titleTemplateRef$ | async)"></ng-container></h3>
<div class="modal-body">
<vdr-dialog-component-outlet [component]="childComponentType"
(create)="onCreate($event)"></vdr-dialog-component-outlet>
</div>
<div class="modal-footer">
<ng-container *ngTemplateOutlet="(buttonsTemplateRef$ | async)"></ng-container>
</div>
</clr-modal>
</div>
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import {
Component,
ContentChild,
ContentChildren,
QueryList,
TemplateRef,
Type,
ViewChild,
ViewChildren,
} from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Component, OnInit, TemplateRef, Type } from '@angular/core';
import { Subject } from 'rxjs';

import {
LocalizationDirectionType,
LocalizationService,
} from '../../../providers/localization/localization.service';
import { Dialog, ModalOptions } from '../../../providers/modal/modal.types';

import { DialogButtonsDirective } from './dialog-buttons.directive';
Expand All @@ -23,13 +18,24 @@ import { DialogButtonsDirective } from './dialog-buttons.directive';
templateUrl: './modal-dialog.component.html',
styleUrls: ['./modal-dialog.component.scss'],
})
export class ModalDialogComponent<T extends Dialog<any>> {
export class ModalDialogComponent<T extends Dialog<any>> implements OnInit {
direction$: LocalizationDirectionType;

childComponentType: Type<T>;
closeModal: (result?: any) => void;
titleTemplateRef$ = new Subject<TemplateRef<any>>();
buttonsTemplateRef$ = new Subject<TemplateRef<any>>();
options?: ModalOptions<T>;

/**
*
*/
constructor(private localizationService: LocalizationService) {}

ngOnInit(): void {
this.direction$ = this.localizationService.direction$;
}

/**
* This callback is invoked when the childComponentType is instantiated in the
* template by the {@link DialogComponentOutletComponent}.
Expand Down