docs(docs-infra): Update navigation status

This commit is contained in:
Matthieu Riegler
2026-06-10 22:04:25 +02:00
committed by Jessica Janiuk
parent 6b3e46a872
commit 59e2041847
6 changed files with 81 additions and 37 deletions
@@ -9,11 +9,17 @@
>
@for (itemGroup of groupItems(navigationItems, preserveOtherCategoryOrder); track $index) {
@let groupLabel = itemGroup[0];
@let items = itemGroup[1];
@let group = itemGroup[1];
@let items = group.items;
@let firstItem = items[0];
@if (groupLabel && collapsableLevel() !== firstItem.level && items.length > 1) {
<li class="docs-navigation-group">{{ groupLabel }}</li>
<li class="docs-navigation-group">
<span>{{ groupLabel }}</span>
<ng-container
*ngTemplateOutlet="itemStatus; context: {$implicit: {status: group.status}}"
/>
</li>
}
@for (item of items; track $index) {
<li
@@ -188,8 +188,12 @@ a,
}
.docs-navigation-group {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.5rem 0.5rem 1rem;
background: var(--septenary-contrast);
&:not(:first-child) {
margin-top: 2rem;
}
@@ -31,6 +31,32 @@ const navigationItems: NavigationItem[] = [
},
];
const navigationItemsWithCategoryStatus: NavigationItem[] = [
{
label: 'Forms',
level: 1,
categoriesStatus: [{'Signal Forms': 'new'}],
children: [],
},
];
navigationItemsWithCategoryStatus[0].children = [
{
label: 'Overview',
path: 'guide/forms',
level: 2,
category: 'Signal Forms',
parent: navigationItemsWithCategoryStatus[0],
},
{
label: 'Signals',
path: 'guide/forms/signals',
level: 2,
category: 'Signal Forms',
parent: navigationItemsWithCategoryStatus[0],
},
];
describe('NavigationList', () => {
let component: NavigationList;
let fixture: ComponentFixture<NavigationList>;
@@ -58,6 +84,20 @@ describe('NavigationList', () => {
expect(nonClickableItem.length).toBe(1);
});
it('should show category status on grouped navigation headers', async () => {
fixture.componentRef.setInput('navigationItems', [...navigationItemsWithCategoryStatus]);
await fixture.whenStable();
const categoryGroup = fixture.debugElement.query(By.css('.docs-navigation-group'));
const categoryStatus = fixture.debugElement.query(
By.css('.docs-navigation-group .docs-new-item'),
);
expect(categoryGroup.nativeElement.innerText).toContain('Signal Forms');
expect(categoryStatus).toBeTruthy();
expect(categoryStatus.nativeElement.innerText).toBe('New');
});
it('should append `docs-navigation-list-dropdown` when isDropdownView is true', async () => {
fixture.componentRef.setInput('isDropdownView', true);
await fixture.whenStable();
@@ -73,14 +73,27 @@ export class NavigationList {
return items.some((item) => !!item.category);
}
private getCategoryStatus(item: NavigationItem, category: string): 'new' | 'updated' | undefined {
const categoriesStatus = item.parent?.categoriesStatus;
if (!categoriesStatus) {
return undefined;
}
return categoriesStatus.find((status) => status[category])?.[category];
}
protected groupItems(
items: NavigationItem[],
preserveOtherCategoryOrder: boolean,
): Map<string, NavigationItem[]> {
): Map<string, {items: NavigationItem[]; status: 'new' | 'updated' | undefined}> {
const hasCategories = this.hasCategories(items);
if (hasCategories) {
const others: NavigationItem[] = [];
const categorizedItems = new Map<string, NavigationItem[]>();
const categorizedItems = new Map<
string,
{items: NavigationItem[]; status: 'new' | 'updated' | undefined}
>();
for (const item of items) {
const category = item.category || 'Other';
if (!preserveOtherCategoryOrder && category === 'Other') {
@@ -88,16 +101,20 @@ export class NavigationList {
continue;
}
if (!categorizedItems.has(category)) {
categorizedItems.set(category, []);
categorizedItems.set(category, {
items: [],
status: this.getCategoryStatus(item, category),
});
}
categorizedItems.get(category)!.push(item);
categorizedItems.get(category)!.items.push(item);
}
if (others.length) {
categorizedItems.set('Other', others);
categorizedItems.set('Other', {items: others, status: undefined});
}
return categorizedItems;
} else {
return new Map([['', items]]);
return new Map([['', {items, status: undefined}]]);
}
}
}
@@ -17,6 +17,7 @@ export interface NavigationItem {
contentPath?: string;
status?: 'new' | 'updated';
category?: string;
categoriesStatus?: Record<string, 'new' | 'updated'>[];
isCrossReferenced?: boolean;
preserveOtherCategoryOrder?: boolean; // true by default
}
@@ -313,13 +313,11 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
label: 'Overview',
path: 'guide/di',
contentPath: 'guide/di/overview',
status: 'updated',
},
{
label: 'Creating and using services',
path: 'guide/di/creating-and-using-services',
contentPath: 'guide/di/creating-and-using-services',
status: 'updated',
},
{
label: 'Lazy loading services',
@@ -331,7 +329,6 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
label: 'Defining dependency providers',
path: 'guide/di/defining-dependency-providers',
contentPath: 'guide/di/defining-dependency-providers',
status: 'updated',
},
{
label: 'Injection context',
@@ -363,7 +360,6 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
},
{
label: 'Routing',
status: 'updated',
children: [
{
label: 'Overview',
@@ -419,7 +415,6 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
label: 'Testing routing and navigation',
path: 'guide/routing/testing',
contentPath: 'guide/routing/testing',
status: 'new',
},
{
label: 'Other routing tasks',
@@ -435,13 +430,11 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
label: 'Rendering strategies',
path: 'guide/routing/rendering-strategies',
contentPath: 'guide/routing/rendering-strategies',
status: 'new',
},
{
label: 'Customizing route behavior',
path: 'guide/routing/customizing-route-behavior',
contentPath: 'guide/routing/customizing-route-behavior',
status: 'new',
},
{
label: 'Router reference',
@@ -459,6 +452,11 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
label: 'Forms',
status: 'updated',
preserveOtherCategoryOrder: true,
categoriesStatus: [
{
'Signal Forms': 'new',
},
],
children: [
{
label: 'Overview',
@@ -471,98 +469,84 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
path: 'guide/forms/signals/overview',
contentPath: 'guide/forms/signals/overview',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Form models',
path: 'guide/forms/signals/models',
contentPath: 'guide/forms/signals/models',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Form model design',
path: 'guide/forms/signals/model-design',
contentPath: 'guide/forms/signals/designing-your-form-model',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Field state management',
path: 'guide/forms/signals/field-state-management',
contentPath: 'guide/forms/signals/field-state-management',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Validation',
path: 'guide/forms/signals/validation',
contentPath: 'guide/forms/signals/validation',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Form logic',
path: 'guide/forms/signals/form-logic',
contentPath: 'guide/forms/signals/form-logic',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Cross-field logic',
path: 'guide/forms/signals/cross-field-logic',
contentPath: 'guide/forms/signals/cross-field-logic',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Form submission',
path: 'guide/forms/signals/form-submission',
contentPath: 'guide/forms/signals/form-submission',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Schemas',
path: 'guide/forms/signals/schemas',
contentPath: 'guide/forms/signals/schemas',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Field metadata',
path: 'guide/forms/signals/field-metadata',
contentPath: 'guide/forms/signals/field-metadata',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Async operations',
path: 'guide/forms/signals/async-operations',
contentPath: 'guide/forms/signals/async-operations',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Custom controls',
path: 'guide/forms/signals/custom-controls',
contentPath: 'guide/forms/signals/custom-controls',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Comparison with other form systems',
path: 'guide/forms/signals/comparison',
contentPath: 'guide/forms/signals/comparison',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Migrating from Reactive Forms',
path: 'guide/forms/signals/migration',
contentPath: 'guide/forms/signals/migration',
category: 'Signal Forms',
status: 'new',
},
{
label: 'Reactive forms',
@@ -887,13 +871,11 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
},
{
label: 'Animations',
status: 'updated',
children: [
{
label: 'Enter and Leave animations',
path: 'guide/animations',
contentPath: 'guide/animations/enter-and-leave',
status: 'new',
},
{
label: 'Complex Animations with CSS',
@@ -1115,7 +1097,6 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
label: 'Style Guide',
path: 'style-guide',
contentPath: 'best-practices/style-guide',
status: 'updated',
},
{
label: 'Security',
@@ -1354,7 +1335,6 @@ export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
label: 'Tailwind',
path: 'guide/tailwind',
contentPath: 'guide/tailwind',
status: 'new',
},
{
label: 'Angular Fire',
@@ -1760,25 +1740,21 @@ export const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [
label: 'NgClass to Class',
path: 'reference/migrations/ngclass-to-class',
contentPath: 'reference/migrations/ngclass-to-class',
status: 'new',
},
{
label: 'NgStyle to Style',
path: 'reference/migrations/ngstyle-to-style',
contentPath: 'reference/migrations/ngstyle-to-style',
status: 'new',
},
{
label: 'Router Testing Module Migration',
path: 'reference/migrations/router-testing-module-migration',
contentPath: 'reference/migrations/router-testing-module-migration',
status: 'new',
},
{
label: 'CommonModule to Standalone',
path: 'reference/migrations/common-to-standalone',
contentPath: 'reference/migrations/common-to-standalone',
status: 'new',
},
],
},