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

implementation (category field): #29563 Change category selection from PrimeNG sidebar to dialog #29770

Merged
merged 7 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
}

.category-list__category-column {
flex: 0 0 30%;
flex: 0 0 38%;
border-right: 1px solid $color-palette-gray-400;
overflow-y: auto;
height: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
} from '../../models/dot-category-field.models';
import { DotCategoryFieldListSkeletonComponent } from '../dot-category-field-list-skeleton/dot-category-field-list-skeleton.component';

export const MINIMUM_CATEGORY_COLUMNS = 4;
export const MINIMUM_CATEGORY_COLUMNS = 3;

const MINIMUM_CATEGORY_WITHOUT_SCROLLING = 3;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,33 @@
<p-sidebar
#sidebar
(onHide)="closedSidebar.emit()"
[(visible)]="visible"
[showCloseIcon]="false"
data-testId="sidebar"
position="right"
styleClass="w-9">
<ng-template pTemplate="header">
<div class="flex flex-row category-field__header">
<button
type="button"
(click)="sidebar.close($event)"
class="p-button-rounded p-button-text"
data-testId="back-btn"
icon="pi pi-angle-left"
pButton></button>
<div class="category-field__header-title" data-testId="sidebar-title">
{{ 'edit.content.category-field.sidebar.header.select-categories' | dm }}
</div>
</div>
</ng-template>

<p-dialog
(onHide)="closedDialog.emit()"
[(visible)]="$isVisible"
data-testId="dialog"
[draggable]="false"
[resizable]="false"
[maximizable]="false"
[modal]="true"
[header]="'edit.content.category-field.dialog.header.select-categories' | dm"
styleClass="category-field__dialog">
<div class="category-field__content h-full w-full">
<div class="category-field__left-pane flex flex-column">
<div class="category-field__search">
<dot-category-field-search
(changeMode)="store.setMode($event)"
(term)="store.search($event)"
@fadeAnimation
[isLoading]="store.isSearchLoading()" />
</div>
<div class="flex-grow-1 category-field__categories">
@if (store.mode() === 'list') {
<dot-category-field-category-list
@fadeAnimation
(itemChecked)="store.updateSelected($event.selected, $event.item)"
(rowClicked)="store.getCategories($event)"
[categories]="store.categoryList()"
[isLoading]="store.isListLoading()"
[isInitialState]="store.isInitSate()"
[selected]="store.selectedCategoriesValues()"
[selected]="store.confirmedCategoriesValues()"
[breadcrumbs]="store.breadcrumbMenu()" />
} @else {
<dot-category-field-search-list
@fadeAnimation
(itemChecked)="store.addSelected($event)"
(removeItem)="store.removeSelected($event)"
[status]="store.searchStatus()"
Expand All @@ -68,15 +52,23 @@
class="p-button p-button-link"
data-testId="clear_all-btn"
pButton>
{{ 'edit.content.category-field.sidebar.button.clear-all' | dm }}
{{ 'edit.content.category-field.dialog.button.clear-all' | dm }}
</button>
</div>
} @else {
<div class="category-field__empty-state" data-testId="category-field__empty-state">
<i class="pi pi-check-square"></i>
<p>{{ 'edit.content.category-field.sidebar.empty-state' | dm }}</p>
<p>{{ 'edit.content.category-field.dialog.empty-state' | dm }}</p>
</div>
}
</div>
</div>
</p-sidebar>
<ng-template pTemplate="footer">
<p-button
(click)="closedDialog.emit()"
[label]="'Cancel' | dm"
styleClass="p-button-outlined"
data-testId="dialog-cancel" />
<p-button (click)="confirmCategories()" [label]="'Apply' | dm" data-testId="dialog-apply" />
</ng-template>
</p-dialog>
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,17 @@
scrollbar-gutter: auto;
}

:host ::ng-deep .p-sidebar-content {
padding: 0;
height: 100%;
overflow: hidden;
:host ::ng-deep {
.category-field__dialog.p-dialog {
min-height: 90%;
height: 100%;
width: 75vw;

.p-dialog-content {
padding: 0;
height: 100%;
}
}
}

.category-field__empty-state {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,37 @@ import { expect, it } from '@jest/globals';
import { byTestId, createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { of } from 'rxjs';

import { Sidebar } from 'primeng/sidebar';
import { Dialog } from 'primeng/dialog';

import { DotHttpErrorManagerService, DotMessageService } from '@dotcms/data-access';

import { DotCategoryFieldSidebarComponent } from './dot-category-field-sidebar.component';
import { DotCategoryFieldDialogComponent } from './dot-category-field-dialog.component';

import { CATEGORY_LIST_MOCK } from '../../mocks/category-field.mocks';
import { CategoriesService } from '../../services/categories.service';
import { CategoryFieldStore } from '../../store/content-category-field.store';
import { DotCategoryFieldCategoryListComponent } from '../dot-category-field-category-list/dot-category-field-category-list.component';
import { DotCategoryFieldSelectedComponent } from '../dot-category-field-selected/dot-category-field-selected.component';

describe('DotEditContentCategoryFieldSidebarComponent', () => {
let spectator: Spectator<DotCategoryFieldSidebarComponent>;
describe('DotCategoryFieldDialogComponent', () => {
let spectator: Spectator<DotCategoryFieldDialogComponent>;
let store: InstanceType<typeof CategoryFieldStore>;

const createComponent = createComponentFactory({
component: DotCategoryFieldSidebarComponent,
component: DotCategoryFieldDialogComponent,
providers: [mockProvider(DotMessageService), CategoryFieldStore]
});

beforeEach(() => {
spectator = createComponent({
props: {
visible: true
},
providers: [
mockProvider(CategoriesService, {
getChildren: jest.fn().mockReturnValue(of(CATEGORY_LIST_MOCK))
}),
mockProvider(DotHttpErrorManagerService)
]
});
spectator.setInput('isVisible', true);

store = spectator.inject(CategoryFieldStore, true);

Expand All @@ -46,8 +44,8 @@ describe('DotEditContentCategoryFieldSidebarComponent', () => {
});

it('should have `visible` property set to `true` by default', () => {
expect(spectator.component.visible).toBe(true);
expect(spectator.query(Sidebar)).not.toBeNull();
expect(spectator.component.$isVisible()).toBe(true);
expect(spectator.query(Dialog)).not.toBeNull();
});

it('should render the selected categories list when there are selected categories', () => {
Expand All @@ -64,15 +62,42 @@ describe('DotEditContentCategoryFieldSidebarComponent', () => {
expect(spectator.query(byTestId('category-field__empty-state'))).not.toBeNull();
});

it('should emit event to close sidebar when "back" button is clicked', () => {
const closedSidebarSpy = jest.spyOn(spectator.component.closedSidebar, 'emit');
const cancelBtn = spectator.query(byTestId('back-btn'));
expect(cancelBtn).not.toBeNull();
it('should have the correct configuration for the dialog.', () => {
const closedDialogSpy = jest.spyOn(spectator.component.closedDialog, 'emit');
const dialog = spectator.query(Dialog);

expect(closedSidebarSpy).not.toHaveBeenCalled();
expect(dialog.draggable).toBe(false);
expect(dialog.resizable).toBe(false);
expect(dialog.modal).toBe(true);
expect(dialog.modal).toBe(true);

spectator.click(cancelBtn);
expect(closedSidebarSpy).toHaveBeenCalled();
dialog.onHide.emit();

expect(closedDialogSpy).toHaveBeenCalled();
});

it('should close the dialog when the close button is clicked', () => {
const closedDialogSpy = jest.spyOn(spectator.component.closedDialog, 'emit');
spectator.click(byTestId('dialog-cancel'));

expect(closedDialogSpy).toHaveBeenCalled();
});

it('should save the changes and apply the categories when the apply button is clicked', () => {
const closedDialogSpy = jest.spyOn(spectator.component.closedDialog, 'emit');
const addConfirmedCategoriesSky = jest.spyOn(store, 'addConfirmedCategories');
const categories = { key: '1234', value: 'test' };
store.addSelected({ key: '1234', value: 'test' });
spectator.detectChanges();

expect(store.selected()).toEqual([categories]);
expect(store.confirmedCategories()).toEqual([]);

spectator.click(byTestId('dialog-apply'));

expect(store.confirmedCategories()).toEqual([categories]);
expect(closedDialogSpy).toHaveBeenCalled();
expect(addConfirmedCategoriesSky).toHaveBeenCalled();
});

it('should render the CategoryFieldCategoryList component', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { animate, state, style, transition, trigger } from '@angular/animations';
import { NgClass } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
EventEmitter,
inject,
Input,
model,
OnDestroy,
OnInit,
Output
output
} from '@angular/core';

import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
import { InputTextModule } from 'primeng/inputtext';
import { SidebarModule } from 'primeng/sidebar';

import { DotMessagePipe } from '@dotcms/ui';

Expand All @@ -26,60 +23,48 @@ import { DotCategoryFieldSearchListComponent } from '../dot-category-field-searc
import { DotCategoryFieldSelectedComponent } from '../dot-category-field-selected/dot-category-field-selected.component';

/**
* The DotCategoryFieldSidebarComponent is a sidebar panel that allows editing of content category field.
* The DotCategoryFieldDialogComponent is a dialog panel that allows editing of content category field.
* It provides interfaces for item selection and click handling, and communicates with a store
* to fetch and update the categories' data.
*
* @property {boolean} visible - Indicates the visibility of the sidebar. Default is `true`.
* @property {EventEmitter<void>} closedSidebar - Event emitted when the sidebar is closed.
* @property {boolean} visible - Indicates the visibility of the dialog. Default is `true`.
* @property {output<void>} closedDialog - Output emitted when the dialog is closed.
*/
@Component({
selector: 'dot-category-field-sidebar',
selector: 'dot-category-field-dialog',
standalone: true,
imports: [
DialogModule,
ButtonModule,
DotMessagePipe,
SidebarModule,
DotCategoryFieldCategoryListComponent,
InputTextModule,
DotCategoryFieldSearchComponent,
DotCategoryFieldSearchListComponent,
DotCategoryFieldSelectedComponent,
NgClass
],
templateUrl: './dot-category-field-sidebar.component.html',
styleUrl: './dot-category-field-sidebar.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
trigger('fadeAnimation', [
state(
'void',
style({
opacity: 0
})
),
transition(':enter, :leave', [animate('50ms ease-in-out')])
])
]
templateUrl: './dot-category-field-dialog.component.html',
styleUrl: './dot-category-field-dialog.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DotCategoryFieldSidebarComponent implements OnInit, OnDestroy {
export class DotCategoryFieldDialogComponent implements OnInit, OnDestroy {
/**
* Indicates the visibility of the sidebar.
* Indicates the visibility of the dialog.
*
* @memberof DotCategoryFieldSidebarComponent
* @memberof DotCategoryFieldDialogComponent
*/
@Input() visible = false;
$isVisible = model<boolean>(false, { alias: 'isVisible' });

/**
* Output that emit if the sidebar is closed
* Output that emit if the Dialog is closed
*/
@Output() closedSidebar = new EventEmitter<void>();
closedDialog = output<void>();

/**
* Store based on the `CategoryFieldStore`.
*
* @memberof DotCategoryFieldSidebarComponent
* @memberof DotCategoryFieldDialogComponent
*/
readonly store = inject(CategoryFieldStore);

Expand All @@ -95,4 +80,9 @@ export class DotCategoryFieldSidebarComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.store.clean();
}

confirmCategories(): void {
this.store.addConfirmedCategories();
this.closedDialog.emit();
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
@if (store.selected().length) {
@if (store.confirmedCategories().length) {
<dot-category-field-chips
data-testId="category-chip-list"
[categories]="store.selected()"
(remove)="store.removeSelected($event)" />
[categories]="store.confirmedCategories()"
(remove)="store.removeConfirmedCategories($event)" />
}

<div class="dot-category-field__select">
<button
type="button"
(click)="openCategoriesSidebar()"
[disabled]="$showCategoriesSidebar()"
(click)="openCategoriesDialog()"
[disabled]="$showCategoriesDialog()"
[label]="'edit.content.category-field.show-categories-dialog' | dm"
class="p-button-sm p-button-text p-button-secondary"
data-testId="show-sidebar-btn"
data-testId="show-dialog-btn"
pButton></button>
</div>

@if ($showCategoriesSidebar()) {
@defer (when $showCategoriesSidebar()) {
<dot-category-field-sidebar
[visible]="$showCategoriesSidebar()"
(closedSidebar)="closeCategoriesSidebar()"
data-testId="sidebar-placeholder" />
@if ($showCategoriesDialog()) {
@defer (when $showCategoriesDialog()) {
<dot-category-field-dialog
[isVisible]="$showCategoriesDialog()"
(closedDialog)="closeCategoriesDialog()"
data-testId="dialog-placeholder" />
}
}
Loading
Loading