Skip to content

Migrate all toolbar components to standalone and use the new syntax #32257

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

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b26549f
refactor(modules): Replace DotToolbarModule with DotToolbarComponent …
nicobytes May 27, 2025
75fa8e9
refactor(dot-dropdown): Enhance dropdown component with signals and c…
nicobytes May 27, 2025
dc50347
Merge branch '32256-new-angular-syntax-in-dottoolbarcomponent' of git…
nicobytes May 28, 2025
3c53e33
refactor(dot-dropdown): Update dropdown component to utilize signals …
nicobytes May 28, 2025
6832eed
chore(dependencies): Update ng-mocks to version 14.13.5 for improved …
nicobytes May 29, 2025
cef84ac
refactor(dot-crumbtrail): Remove DotCrumbtrailModule as part of compo…
nicobytes May 29, 2025
1247a9d
refactor(components): Update templates to utilize new Angular syntax …
nicobytes May 29, 2025
f796e66
Merge branch 'main' into 32256-new-angular-syntax-in-dottoolbarcomponent
nicobytes May 29, 2025
5c7f066
refactor(dot-notifications): Reorganize imports for improved clarity …
nicobytes May 29, 2025
52dda86
Merge branch '32256-new-angular-syntax-in-dottoolbarcomponent' of git…
nicobytes May 29, 2025
f83e2cf
refactor(dot-notifications): Update button import and fix template sy…
nicobytes May 29, 2025
8e9f58a
refactor(dot-notifications): Update button import and fix template sy…
nicobytes May 29, 2025
faa14e7
Update core-web/apps/dotcms-ui/src/app/view/components/_common/dot-dr…
nicobytes May 29, 2025
af482c0
refactor(components): Update templates to utilize new Angular syntax …
nicobytes May 29, 2025
acd9298
chore(dependencies): downgrade ng-mocks to version 14.12.1 to resolve…
nicobytes May 30, 2025
b8c551b
sync with master
nicobytes Jun 26, 2025
ad55a5e
chore(dotcms-ui): Refactor DotToolbar and SharedModule components
nicobytes Jun 26, 2025
9704f2a
Merge branch 'main' into 32256-new-angular-syntax-in-dottoolbarcomponent
nicobytes Jun 26, 2025
b58834f
refactor(dotcms-ui): Replace DotDropdownComponent with DotDropdownMod…
nicobytes Jun 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(dot-dropdown): Enhance dropdown component with signals and c…
…omputed properties for improved state management
  • Loading branch information
nicobytes committed May 27, 2025
commit 75fa8e9e09ae9b592eebfc4b4a5fb0ed11f8bb8c
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
@let disabled = $disabledState();
@let show = $show();
@let icon = $icon();
@let title = $title();

@if (show) {
<div (click)="onToggle()" class="dot-mask"></div>
}
Expand All @@ -23,7 +28,7 @@
}

@if (show) {
<div [ngStyle]="positionStyle" class="dropdown-content" [@enterAnimation]="show">
<ng-content></ng-content>
<div [style]="$style()" class="dropdown-content" [@enterAnimation]="show">
<ng-content />
</div>
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { animate, style, transition, trigger } from '@angular/animations';
import {
Component,
computed,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use dropdown from primeNg instead of DotDropdownComponent

ElementRef,
EventEmitter,
HostListener,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges
inject,
input,
signal,
output
} from '@angular/core';

import { ButtonModule } from 'primeng/button';
Expand All @@ -32,75 +31,141 @@ import { ButtonModule } from 'primeng/button';
standalone: true,
imports: [ButtonModule]
})
export class DotDropdownComponent implements OnChanges, OnInit {
@Input()
disabled = false;

@Input()
icon = null;

@Input()
title = null;

@Input() position: 'left' | 'right' = 'left';

@Input()
inverted = false;

@Output()
wasOpen = new EventEmitter<never>();

@Output()
toggle = new EventEmitter<boolean>();

@Output()
shutdown = new EventEmitter<never>();

show = false;
positionStyle = {};

constructor(private elementRef: ElementRef) {}

ngOnInit() {
this.positionStyle[this.position] = '0';
}

ngOnChanges(changes: SimpleChanges): void {
if (changes.disabled && this.icon) {
this.disabled = changes.disabled.currentValue ? true : null;
}
}

export class DotDropdownComponent {
/** Reference to the component's DOM element for click detection */
readonly #elementRef = inject(ElementRef);

/**
* Controls whether the dropdown trigger button is disabled.
* When true, prevents user interaction with the dropdown.
* @default false
*/
$disabled = input(false, { alias: 'disabled' });

/**
* Icon to display on the dropdown trigger button.
* Accepts PrimeNG icon classes or null for no icon.
* @default null
*/
$icon = input(null, { alias: 'icon' });

/**
* Title text to display on the dropdown trigger button.
* Can be null if only an icon is desired.
* @default null
*/
$title = input(null, { alias: 'title' });

/**
* Position of the dropdown content relative to the trigger button.
* 'left' aligns content to the left edge, 'right' to the right edge.
* @default 'left'
*/
$position = input<'left' | 'right'>('left', { alias: 'position' });

/**
* Controls the visual styling theme of the dropdown.
* When true, applies inverted color scheme.
* @default false
*/
$inverted = input(false, { alias: 'inverted' });

/**
* Computed CSS style object for positioning the dropdown content.
* Dynamically sets left or right positioning based on $position input.
* @returns CSS style object with positioning property
*/
$style = computed(() => {
return {
[this.$position()]: '0'
};
});

/**
* Computed boolean indicating if the dropdown should be in disabled state.
* Returns true only when both disabled is true AND an icon is present.
* @returns true if dropdown should be disabled with icon, false otherwise
*/
$disabledState = computed(() => {
const icon = this.$icon();
const disabled = this.$disabled();

return disabled && icon ? true : false;
});

/**
* Event emitted when the dropdown is opened.
* Useful for triggering actions when dropdown becomes visible.
*/
wasOpen = output<void>();

/**
* Event emitted whenever the dropdown visibility state changes.
* Emits the current visibility state (true for open, false for closed).
*/
toggle = output<boolean>();

/**
* Event emitted when the dropdown is closed.
* Useful for cleanup actions when dropdown becomes hidden.
*/
shutdown = output<void>();

/**
* Signal tracking the current visibility state of the dropdown.
* true when dropdown is open, false when closed.
* @default false
*/
$show = signal(false);

/**
* Host listener that handles clicks outside the dropdown component.
* Automatically closes the dropdown when user clicks outside of it.
* Traverses the DOM tree to determine if click occurred inside component.
*
* @param $event - The mouse click event from the document
*/
@HostListener('document:click', ['$event'])
handleClick($event) {
let clickedComponent = $event.target;
let inside = false;
do {
if (clickedComponent === this.elementRef.nativeElement) {
if (clickedComponent === this.#elementRef.nativeElement) {
inside = true;
}

clickedComponent = clickedComponent.parentNode;
} while (clickedComponent);

if (!inside) {
this.show = false;
this.$show.set(false);
}
}

/**
* Programmatically closes the dropdown by setting visibility to false.
* Can be called from parent components or internal logic to hide dropdown.
*/
closeIt(): void {
this.show = false;
this.$show.set(false);
}

/**
* Toggles the dropdown visibility state and emits appropriate events.
* - Flips the current $show state
* - Emits wasOpen when dropdown opens
* - Emits shutdown when dropdown closes
* - Always emits toggle with current state
*/
onToggle(): void {
this.show = !this.show;
this.$show.update((value) => !value);

if (this.show) {
if (this.$show()) {
this.wasOpen.emit();
} else {
this.shutdown.emit();
}

this.toggle.emit(this.show);
this.toggle.emit(this.$show());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DatePipe, LowerCasePipe, NgClass } from '@angular/common';
import { CommonModule } from '@angular/common';
import {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move this component to the UI lib?

Component,
EventEmitter,
Expand All @@ -7,9 +7,9 @@ import {
OnInit,
Output,
Signal,
ViewChild,
inject,
signal
signal,
viewChild
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

Expand All @@ -30,14 +30,14 @@ import { DotMessagePipe } from '@dotcms/ui';
templateUrl: './dot-toolbar-announcements.component.html',
styleUrls: ['./dot-toolbar-announcements.component.scss'],
standalone: true,
imports: [NgClass, DotMessagePipe, OverlayPanelModule, LowerCasePipe, DatePipe],
imports: [CommonModule, DotMessagePipe, OverlayPanelModule],
providers: [AnnouncementsStore]
})
export class DotToolbarAnnouncementsComponent implements OnInit, OnChanges {
announcementsStore = inject(AnnouncementsStore);
dotMessageService = inject(DotMessageService);
siteService = inject(SiteService);
@ViewChild('toolbarAnnouncements', { static: true }) toolbarAnnouncements: OverlayPanel;
$toolbarAnnouncements = viewChild.required<OverlayPanel>('toolbarAnnouncements');
@Output() hideMenu = new EventEmitter();

@Input() showUnreadAnnouncement: boolean;
Expand Down Expand Up @@ -85,7 +85,7 @@ export class DotToolbarAnnouncementsComponent implements OnInit, OnChanges {
*/
toggleDialog(event): void {
this.showMask.update((value) => !value);
this.toolbarAnnouncements.toggle(event);
this.$toolbarAnnouncements().toggle(event);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { FeaturedFlags } from '@dotcms/dotcms-models';
import { DotMessagePipe } from '@dotcms/ui';
import { INotification } from '@models/notifications';


import { DotNotificationsListComponent } from './components/dot-notifications/dot-notifications.component';

import { DotToolbarAnnouncementsComponent } from '../dot-toolbar-announcements/dot-toolbar-announcements.component';
Expand All @@ -34,6 +33,11 @@ import { DotToolbarAnnouncementsComponent } from '../dot-toolbar-announcements/d
providers: [AnnouncementsStore]
})
export class DotToolbarNotificationsComponent implements OnInit {
iframeOverlayService = inject(IframeOverlayService);
private dotcmsEventsService = inject(DotcmsEventsService);
private loginService = inject(LoginService);
private notificationService = inject(NotificationsService);

readonly #announcementsStore = inject(AnnouncementsStore);

@ViewChild(DotDropdownComponent, { static: true }) dropdown: DotDropdownComponent;
Expand All @@ -51,13 +55,6 @@ export class DotToolbarNotificationsComponent implements OnInit {

showUnreadAnnouncement = this.#announcementsStore.showUnreadAnnouncement;

constructor(
public iframeOverlayService: IframeOverlayService,
private dotcmsEventsService: DotcmsEventsService,
private loginService: LoginService,
private notificationService: NotificationsService
) {}

ngOnInit(): void {
this.getNotifications();
this.subscribeToNotifications();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ComponentStore } from '@ngrx/component-store';
import { Observable } from 'rxjs';

import { Inject, Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';

import { MenuItem } from 'primeng/api';

Expand Down Expand Up @@ -34,19 +34,19 @@ const INITIAL_STATE: DotToolbarUserState = {

@Injectable()
export class DotToolbarUserStore extends ComponentStore<DotToolbarUserState> {
private loginService = inject(LoginService);
private dotMessageService = inject(DotMessageService);
private dotNavigationService = inject(DotNavigationService);
private loggerService = inject(LoggerService);
private location = inject<Location>(LOCATION_TOKEN);

private readonly FINAL_LOGOUT_URL = `${LOGOUT_URL}?r=${new Date().getTime()}`;

readonly vm$: Observable<DotToolbarUserState> = this.select((state) => state).pipe(
filter((vm) => !!vm.userData.email)
);

constructor(
private loginService: LoginService,
private dotMessageService: DotMessageService,
private dotNavigationService: DotNavigationService,
private loggerService: LoggerService,
@Inject(LOCATION_TOKEN) private location: Location
) {
constructor() {
super(INITIAL_STATE);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
? 'pi pi-bars'
: 'pi pi-arrow-left'
"
styleClass="p-button-rounded p-button-text"></p-button>
styleClass="p-button-rounded p-button-text" />
</div>
<div
[class.toolbar__logo-wrapper--collapsed]="dotNavigationService.collapsed$ | async"
Expand All @@ -26,7 +26,7 @@
<span class="toolbar__logo-bg"></span>
</div>
</div>
<dot-crumbtrail class="toolbar__crumbtrail"></dot-crumbtrail>
<dot-crumbtrail class="toolbar__crumbtrail" />
</div>
<div class="p-toolbar-group-right">
<dot-site-selector
Expand All @@ -37,8 +37,8 @@
class="toolbar__site-selector"
#siteSelector
cssClass="d-secondary"
width="200px"></dot-site-selector>
<dot-toolbar-notifications></dot-toolbar-notifications>
<dot-toolbar-user></dot-toolbar-user>
width="200px" />
<dot-toolbar-notifications />
<dot-toolbar-user />
</div>
</p-toolbar>
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,18 @@ import { DotNavigationService } from '../dot-navigation/services/dot-navigation.
]
})
export class DotToolbarComponent implements OnInit {
private dotRouterService = inject(DotRouterService);
private dotcmsEventsService = inject(DotcmsEventsService);
private siteService = inject(SiteService);
dotNavigationService = inject(DotNavigationService);
iframeOverlayService = inject(IframeOverlayService);

readonly #dotNavLogoService = inject(DotNavLogoService);

@Input()
collapsed: boolean;
logo$: BehaviorSubject<string> = this.#dotNavLogoService.navBarLogo$;

constructor(
private dotRouterService: DotRouterService,
private dotcmsEventsService: DotcmsEventsService,
private siteService: SiteService,
public dotNavigationService: DotNavigationService,
public iframeOverlayService: IframeOverlayService
) {}

ngOnInit(): void {
this.dotcmsEventsService.subscribeTo<Site>('ARCHIVE_SITE').subscribe((data: Site) => {
if (data.hostname === this.siteService.currentSite.hostname && data.archived) {
Expand Down
Loading