Skip to content

Commit

Permalink
feat(editor-content): Limit the amount categories (#29208)
Browse files Browse the repository at this point in the history
### Parent Issue

#29183

### Proposed Changes

* Create a new component to handle the category list as chips.
* Add the necessary logic to display a btn with a maximum number of
chips.

### Checklist
- [x] Tests
- [x] Translations
- [x] Security Implications Contemplated (add notes if applicable)


### Screenshots



https://github.com/user-attachments/assets/c2f20ec9-c420-4336-8cf4-8f01c6af55b4
  • Loading branch information
nicobytes authored Jul 12, 2024
1 parent c58feda commit 20af29a
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 20 deletions.
1 change: 0 additions & 1 deletion core-web/libs/edit-content/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export default {
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {},
coverageDirectory: '../../coverage/libs/edit-content',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="flex gap-3 align-items-center flex-wrap" data-testId="category-list">
@for (category of $categoriesToShow(); track category.key) {
<p-chip
[pTooltip]="category.value"
[removable]="true"
[label]="category.value"
tooltipPosition="top"
styleClass="p-chip-sm" />
} @if ($showAllBtn()) {
<button
(click)="toogleShowAll()"
[label]="$btnLabel()"
class="p-button-sm p-button-text p-button-secondary"
data-testId="show-btn"
pButton></button>
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';

import { ButtonModule, ButtonDirective } from 'primeng/button';
import { ChipModule, Chip } from 'primeng/chip';

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

import { DotCategoryFieldChipsComponent } from './dot-category-field-chips.component';

import { MAX_CHIPS } from '../../dot-edit-content-category-field.const';
import { CATEGORIES_KEY_VALUE, CATEGORY_MESSAGE_MOCK } from '../../mocks/category-field.mocks';

describe('DotCategoryFieldChipsComponent', () => {
let spectator: Spectator<DotCategoryFieldChipsComponent>;

const createComponent = createComponentFactory({
component: DotCategoryFieldChipsComponent,
providers: [
{
provide: DotMessageService,
useValue: CATEGORY_MESSAGE_MOCK
}
],
imports: [ChipModule, ButtonModule]
});

beforeEach(() => {
spectator = createComponent({
detectChanges: false,
props: {
categories: CATEGORIES_KEY_VALUE
} as unknown as DotCategoryFieldChipsComponent
});
});

it('should be created', () => {
spectator.detectChanges();
expect(spectator.component).toBeTruthy();
});

it('should the max input be equal to constant by default', () => {
spectator.detectChanges();
expect(spectator.component.$max()).toBe(MAX_CHIPS);
});

it('should be show a max of categories', () => {
spectator.setInput('max', 2);
spectator.detectChanges();
const chips = spectator.queryAll(Chip);
expect(chips.length).toBe(2);
});

it('should be show all categories', () => {
spectator.setInput('max', 2);
spectator.component.$showAll.set(true);
spectator.detectChanges();
const chips = spectator.queryAll(Chip);
expect(chips.length).toBe(CATEGORIES_KEY_VALUE.length);
});

it('should be show the more btn with the proper label', () => {
spectator.setInput('max', 2);
spectator.detectChanges();
const showBtn = spectator.query(ButtonDirective);
const size = spectator.component.$categories().length - spectator.component.$max();
expect(showBtn.label).toBe(`${size} More`);
});

it('should be show the less btn with the proper label', () => {
spectator.setInput('max', 2);
spectator.component.$showAll.set(true);
spectator.detectChanges();
const showBtn = spectator.query(ButtonDirective);
expect(showBtn.label).toBe(`Less`);
});

it('should not show a btn and the label be null', () => {
spectator.setInput('max', CATEGORIES_KEY_VALUE.length + 1);
spectator.detectChanges();
const showBtn = spectator.query(ButtonDirective);
const label = spectator.component.$btnLabel();
expect(showBtn).toBeNull();
expect(label).toBeNull();
});

describe('toogleShowAll', () => {
it('should set showAll to true', () => {
spectator.component.$showAll.set(false);
spectator.component.toogleShowAll();
expect(spectator.component.$showAll()).toBe(true);
});

it('should set showAll to false', () => {
spectator.component.$showAll.set(true);
spectator.component.toogleShowAll();
expect(spectator.component.$showAll()).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { ChangeDetectionStrategy, Component, computed, inject, input, signal } from '@angular/core';

import { ButtonModule } from 'primeng/button';
import { ChipModule } from 'primeng/chip';
import { TooltipModule } from 'primeng/tooltip';

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

import { MAX_CHIPS } from '../../dot-edit-content-category-field.const';
import { DotCategoryFieldKeyValueObj } from '../../models/dot-category-field.models';

/**
* Represents the Dot Category Field Chips component.
*
* @export
* @class DotCategoryFieldChipsComponent
*/
@Component({
selector: 'dot-category-field-chips',
standalone: true,
imports: [ButtonModule, ChipModule, TooltipModule],
templateUrl: './dot-category-field-chips.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DotCategoryFieldChipsComponent {
/**
* Represents the variable 'dotMessageService' which is of type 'DotMessageService'.
*
* @memberof DotCategoryFieldChipsComponent
*/
readonly #dotMessageService = inject(DotMessageService);
/**
* Represents the variable 'showAll' which is of type 'signal<boolean>'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$showAll = signal(false);
/**
* Represents the variable 'max' which is of type 'number'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$max = input<number>(MAX_CHIPS, { alias: 'max' });
/**
* Represents the variable 'categories' which is of type 'DotCategoryFieldKeyValueObj[]'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$categories = input.required<DotCategoryFieldKeyValueObj[]>({ alias: 'categories' });
/**
* Represents the variable 'label' which is of type 'string'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$categoriesToShow = computed(() => {
const categories = this.$categories();
if (this.$showAll()) {
return categories;
}

return categories.slice(0, this.$max());
});
/**
* Represents the variable '$showAllBtn' which is of type 'computed<boolean>'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$showAllBtn = computed(() => {
const size = this.$categories().length;

if (size > this.$max()) {
return true;
}

return false;
});
/**
* Represents the variable 'btnLabel' which is of type 'computed<string>'.
*
* @memberof DotCategoryFieldChipsComponent
*/
$btnLabel = computed(() => {
const size = this.$categories().length;
const max = this.$max();

if (this.$showAll()) {
return this.#dotMessageService.get('edit.content.category-field.list.show.less');
}

if (size > max) {
return this.#dotMessageService.get(
'edit.content.category-field.list.show.more',
`${size - max}`
);
}

return null;
});
/**
* Method to toogle the show all categories.
*
* @memberof DotCategoryFieldChipsComponent
*/
toogleShowAll(): void {
this.$showAll.update((showAll) => !showAll);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { DotMessagePipe } from '@dotcms/ui';

import { CategoryFieldStore } from '../../store/content-category-field.store';
import { DotCategoryFieldCategoryListComponent } from '../dot-category-field-category-list/dot-category-field-category-list.component';

/**
* The DotCategoryFieldSidebarComponent is a sidebar panel that allows editing of content category field.
* It provides interfaces for item selection and click handling, and communicates with a store
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
@if (store.selected().length) {
<div class="dot-category-field__categories" data-testId="category-chip-list">
@for (category of store.selected(); track category.key) {
<p-chip
[pTooltip]="category.value"
[removable]="true"
[label]="category.value"
tooltipPosition="top"
styleClass="p-chip-sm" />
}
</div>
<div class="dot-category-field__categories" data-testId="category-chip-list">
<dot-category-field-chips [categories]="store.selected()" />
</div>
}

<div class="dot-category-field__select">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ControlContainer, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';

import { ButtonModule } from 'primeng/button';
import { ChipModule } from 'primeng/chip';
import { ChipsModule } from 'primeng/chips';
import { TooltipModule } from 'primeng/tooltip';

import { delay } from 'rxjs/operators';

import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models';
import { DotDynamicDirective, DotMessagePipe } from '@dotcms/ui';

import { DotCategoryFieldChipsComponent } from './components/dot-category-field-chips/dot-category-field-chips.component';
import { DotCategoryFieldSidebarComponent } from './components/dot-category-field-sidebar/dot-category-field-sidebar.component';
import { CLOSE_SIDEBAR_CSS_DELAY_MS } from './dot-edit-content-category-field.const';
import { CategoriesService } from './services/categories.service';
Expand All @@ -40,14 +38,12 @@ import { CategoryFieldStore } from './store/content-category-field.store';
selector: 'dot-edit-content-category-field',
standalone: true,
imports: [
ChipsModule,
ReactiveFormsModule,
ButtonModule,
ChipModule,
NgClass,
TooltipModule,
DotMessagePipe,
DotDynamicDirective
DotDynamicDirective,
DotCategoryFieldChipsComponent
],
templateUrl: './dot-edit-content-category-field.component.html',
styleUrl: './dot-edit-content-category-field.component.scss',
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const CLOSE_SIDEBAR_CSS_DELAY_MS = 300;

export const MAX_CHIPS = 10;
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models';
import { MockDotMessageService } from '@dotcms/utils-testing';

import { DotCategoryFieldCategory } from '../models/dot-category-field.models';
import {
DotCategoryFieldCategory,
DotCategoryFieldKeyValueObj
} from '../models/dot-category-field.models';

export const CATEGORY_FIELD_VARIABLE_NAME = 'categorias';

Expand Down Expand Up @@ -203,3 +207,43 @@ export const CATEGORY_LIST_MOCK: DotCategoryFieldCategory[][] = [
* Represent the selected categories
*/
export const SELECTED_LIST_MOCK = [CATEGORY_LEVEL_1[1].inode, CATEGORY_LEVEL_1[2].inode];

export const CATEGORIES_KEY_VALUE: DotCategoryFieldKeyValueObj[] = [
{
key: '0ab5e687775e4793679970e561380560',
value: 'Electrical',
path: 'Electrical'
},
{
key: 'cb83dc32c0a198fd0ca427b3b587f4ce',
value: 'Doors & Windows',
path: 'Doors & Windows'
},
{
key: '1f208488057007cedda0e0b5d52ee3b3',
value: 'Cleaning Supplies',
path: 'Cleaning Supplies'
},
{
key: 'd2fb8e67c390e3b84cd613fa15aad5d4',
value: 'Concrete & Cement',
path: 'Concrete & Cement'
},
{
key: '3a3effac9f26593810c8687e692817a6',
value: 'Flooring',
path: 'Flooring'
},
{
key: '977ba2c4e2af65e303c748ec39f0f1ca',
value: 'Garage Organization',
path: 'Garage Organization'
}
];

const MESSAGES_MOCK = {
'edit.content.category-field.list.show.less': 'Less',
'edit.content.category-field.list.show.more': '{0} More'
};

export const CATEGORY_MESSAGE_MOCK = new MockDotMessageService(MESSAGES_MOCK);
2 changes: 2 additions & 0 deletions dotCMS/src/main/webapp/WEB-INF/messages/Language.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5746,3 +5746,5 @@ content.type.form.banner.message= Enable <b>Edit Content Beta</b> for a fresh ed
edit.content.category-field.show-categories-dialog=Select
edit.content.category-field.sidebar.header.select-categories=Select categories
edit.content.category-field.sidebar.button.clear-all=Clear all
edit.content.category-field.list.show.more={0} More
edit.content.category-field.list.show.less=Less

0 comments on commit 20af29a

Please sign in to comment.