mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
💡 docs(shell): add JSDoc documentation for shell components and services
Add comprehensive JSDoc comments to shell layout, navigation, and tabs components to improve code documentation and IDE support. - Document TabsCollabsedService with usage examples - Document ShellLayoutComponent with feature descriptions - Document ShellNavigationItemComponent with signal and method descriptions - Document ShellNavigationSubItemComponent with route resolution details - Document navigations configuration with route type explanations
This commit is contained in:
@@ -1,18 +1,47 @@
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
/**
|
||||
* Service for managing the collapsed/expanded state of the shell tabs bar.
|
||||
*
|
||||
* The tabs bar can switch between compact (collapsed) and expanded modes
|
||||
* based on user proximity. This service provides a centralized state
|
||||
* that can be accessed by both the tabs component and the shell layout.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* readonly tabsCollapsed = inject(TabsCollabsedService);
|
||||
*
|
||||
* // Read current state
|
||||
* const isCollapsed = this.tabsCollapsed.get();
|
||||
*
|
||||
* // Set state
|
||||
* this.tabsCollapsed.set(true);
|
||||
*
|
||||
* // Toggle state
|
||||
* this.tabsCollapsed.toggle();
|
||||
* ```
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TabsCollabsedService {
|
||||
#logger = logger({ service: 'TabsService' });
|
||||
#state = signal<boolean>(false);
|
||||
|
||||
/** Readonly signal exposing the current collapsed state. */
|
||||
readonly get = this.#state.asReadonly();
|
||||
|
||||
/** Toggles the collapsed state between true and false. */
|
||||
toggle(): void {
|
||||
this.#state.update((state) => !state);
|
||||
this.#logger.debug('Tabs toggled', () => ({ isOpen: this.#state() }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collapsed state to a specific value.
|
||||
* No-op if the state is already equal to the provided value.
|
||||
*
|
||||
* @param state - The new collapsed state (true = collapsed, false = expanded)
|
||||
*/
|
||||
set(state: boolean): void {
|
||||
if (this.#state() === state) {
|
||||
return;
|
||||
|
||||
@@ -21,6 +21,23 @@ import {
|
||||
UiElementSizeObserverDirective,
|
||||
} from '@isa/ui/layout';
|
||||
|
||||
/**
|
||||
* Root layout component for the application shell.
|
||||
*
|
||||
* Composes the header, navigation, tabs, and main content area into a
|
||||
* responsive layout. Handles:
|
||||
* - Responsive navigation visibility (auto-hide on tablet breakpoint)
|
||||
* - Click-outside-to-close behavior for mobile navigation
|
||||
* - Dynamic positioning based on header and navigation sizes
|
||||
* - Font size scaling for accessibility
|
||||
*
|
||||
* @example
|
||||
* ```html
|
||||
* <shell-layout>
|
||||
* <router-outlet />
|
||||
* </shell-layout>
|
||||
* ```
|
||||
*/
|
||||
@Component({
|
||||
selector: 'shell-layout',
|
||||
standalone: true,
|
||||
@@ -39,12 +56,19 @@ export class ShellLayoutComponent {
|
||||
#navigationService = inject(NavigationService);
|
||||
#elementRef = inject(ElementRef);
|
||||
|
||||
/** Service for managing tabs collapsed state. */
|
||||
readonly tabsCollabesd = inject(TabsCollabsedService);
|
||||
|
||||
/** Service for managing font size scaling. */
|
||||
readonly fontSizeService = inject(FontSizeService);
|
||||
|
||||
/** Signal indicating if the viewport is at or below tablet breakpoint. */
|
||||
readonly tablet = breakpoint(Breakpoint.Tablet);
|
||||
|
||||
/**
|
||||
* Computed signal determining if navigation should be rendered.
|
||||
* Always visible on desktop; toggled visibility on tablet/mobile.
|
||||
*/
|
||||
readonly renderNavigation = computed(() => {
|
||||
const tablet = this.tablet();
|
||||
|
||||
@@ -55,6 +79,10 @@ export class ShellLayoutComponent {
|
||||
return this.#navigationService.get();
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles clicks outside the navigation to close it on tablet/mobile.
|
||||
* Ignores clicks on the navigation toggle button.
|
||||
*/
|
||||
@HostListener('document:click', ['$event'])
|
||||
onDocumentClick(event: MouseEvent): void {
|
||||
if (!this.tablet() || !this.#navigationService.get()) return;
|
||||
@@ -70,6 +98,7 @@ export class ShellLayoutComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/** Closes navigation on main content scroll (tablet/mobile only). */
|
||||
onMainScroll(): void {
|
||||
if (!this.tablet() || !this.#navigationService.get()) return;
|
||||
this.#navigationService.set(false);
|
||||
|
||||
@@ -20,6 +20,25 @@ import { NgTemplateOutlet } from '@angular/common';
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router';
|
||||
import { ShellNavigationSubItemComponent } from '../navigation-sub-item/navigation-sub-item.component';
|
||||
|
||||
/**
|
||||
* Navigation item component for the shell sidebar.
|
||||
*
|
||||
* Renders a single navigation item with optional icon, expandable submenu,
|
||||
* and activity indicator. Supports both direct navigation routes and
|
||||
* expandable groups with child routes.
|
||||
*
|
||||
* Features:
|
||||
* - Icon display with sanitized SVG content
|
||||
* - Expandable/collapsible submenu for child routes
|
||||
* - Activity indicators (red dot) that bubble up from children when collapsed
|
||||
* - Auto-expansion when a child route becomes active
|
||||
* - Dynamic route resolution (supports factory functions and signals)
|
||||
*
|
||||
* @example
|
||||
* ```html
|
||||
* <shell-navigation-item [item]="navigationItem" />
|
||||
* ```
|
||||
*/
|
||||
@Component({
|
||||
selector: 'shell-navigation-item',
|
||||
templateUrl: './navigation-item.component.html',
|
||||
@@ -43,12 +62,16 @@ export class ShellNavigationItemComponent {
|
||||
#sanitizer = inject(DomSanitizer);
|
||||
#navigationService = inject(NavigationService);
|
||||
|
||||
/** Query for all child sub-item components. */
|
||||
readonly subItems = viewChildren(ShellNavigationSubItemComponent);
|
||||
|
||||
/** Whether the submenu is currently expanded. */
|
||||
expanded = signal(false);
|
||||
|
||||
/** The navigation item configuration to render. */
|
||||
readonly item = input.required<NavigationItem>();
|
||||
|
||||
/** Sanitized HTML content for the icon SVG. */
|
||||
sanitizedHtml = computed(() => {
|
||||
const icon = this.item().icon;
|
||||
|
||||
@@ -59,14 +82,20 @@ export class ShellNavigationItemComponent {
|
||||
return this.#sanitizer.bypassSecurityTrustHtml(icon);
|
||||
});
|
||||
|
||||
/** Whether this item has child routes. */
|
||||
hasChildren = computed(() => {
|
||||
return (this.item().childRoutes?.length ?? 0) > 0;
|
||||
});
|
||||
|
||||
/** Whether any child route is currently active. */
|
||||
hasActiveChild = computed(() => {
|
||||
return this.subItems().some((subItem) => subItem.isActive());
|
||||
});
|
||||
|
||||
/**
|
||||
* Resolves the indicator value, handling factory functions and signals.
|
||||
* Returns the current indicator state (truthy = show red dot).
|
||||
*/
|
||||
readonly resolvedIndicator = computed(() => {
|
||||
const indicator = this.item().indicator;
|
||||
if (typeof indicator === 'function') {
|
||||
@@ -76,25 +105,33 @@ export class ShellNavigationItemComponent {
|
||||
return indicator;
|
||||
});
|
||||
|
||||
/** Whether any child has an active indicator. */
|
||||
readonly hasChildIndicator = computed(() => {
|
||||
return this.subItems().some((subItem) => subItem.resolvedIndicator());
|
||||
});
|
||||
|
||||
/**
|
||||
* Determines if the indicator dot should be shown.
|
||||
* Shows if this item has an indicator, or if a child has one and the menu is collapsed.
|
||||
*/
|
||||
readonly showIndicator = computed(() => {
|
||||
// Show indicator if:
|
||||
// 1. This item has its own indicator, OR
|
||||
// 2. A child has an indicator AND the menu is collapsed (bubble up)
|
||||
return this.resolvedIndicator() || (this.hasChildIndicator() && !this.expanded());
|
||||
});
|
||||
|
||||
/** Toggles the expanded state of the submenu. */
|
||||
toggleExpand(): void {
|
||||
this.expanded.update((value) => !value);
|
||||
}
|
||||
|
||||
/** Closes the navigation sidebar (used on tablet/mobile after selection). */
|
||||
closeNavigation(): void {
|
||||
this.#navigationService.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the first child route for fallback navigation.
|
||||
* Used when the item has no direct route but has child routes.
|
||||
*/
|
||||
readonly firstChildRoute = computed(() => {
|
||||
const childRoutes = this.item().childRoutes;
|
||||
if (!childRoutes?.length) return undefined;
|
||||
@@ -107,6 +144,10 @@ export class ShellNavigationItemComponent {
|
||||
return firstChild;
|
||||
});
|
||||
|
||||
/**
|
||||
* Resolves the navigation route, handling factory functions and signals.
|
||||
* Falls back to the first child route if no direct route is defined.
|
||||
*/
|
||||
readonly route = computed(() => {
|
||||
const route = this.item().route;
|
||||
|
||||
@@ -115,14 +156,12 @@ export class ShellNavigationItemComponent {
|
||||
return isSignal(result) ? result() : result;
|
||||
}
|
||||
|
||||
// Fall back to first child route if no direct route
|
||||
return route ?? this.firstChildRoute();
|
||||
});
|
||||
|
||||
constructor() {
|
||||
// Auto-expand when a child route becomes active.
|
||||
// This is a valid effect use case: one-way sync of UI state based on router state.
|
||||
// It only expands (never auto-collapses), preserving user's manual toggle preference.
|
||||
// Valid effect: one-way sync of UI state from router state (only expands, never auto-collapses).
|
||||
effect(() => {
|
||||
const subItems = this.subItems();
|
||||
if (!subItems.length) return;
|
||||
|
||||
@@ -19,6 +19,18 @@ import {
|
||||
NavigationRouteWithLabelFn,
|
||||
} from '../../types';
|
||||
|
||||
/**
|
||||
* Navigation sub-item component for child routes within a navigation item.
|
||||
*
|
||||
* Renders a single child route link within an expanded navigation item submenu.
|
||||
* Supports dynamic route resolution (factory functions and signals), activity
|
||||
* indicators, and automatic active state detection.
|
||||
*
|
||||
* @example
|
||||
* ```html
|
||||
* <shell-navigation-sub-item [item]="childRoute" />
|
||||
* ```
|
||||
*/
|
||||
@Component({
|
||||
selector: 'shell-navigation-sub-item',
|
||||
templateUrl: './navigation-sub-item.component.html',
|
||||
@@ -30,10 +42,14 @@ export class ShellNavigationSubItemComponent {
|
||||
#injector = inject(Injector);
|
||||
#navigationService = inject(NavigationService);
|
||||
|
||||
/** Reference to the RouterLinkActive directive for active state detection. */
|
||||
readonly routerLinkActive = viewChild(RouterLinkActive);
|
||||
|
||||
/** Signal tracking whether this sub-item's route is currently active. */
|
||||
readonly isActive = signal(false);
|
||||
|
||||
constructor() {
|
||||
// Initialize active state after render to capture initial router state
|
||||
afterNextRender(() => {
|
||||
const rla = this.routerLinkActive();
|
||||
if (rla?.isActive) {
|
||||
@@ -42,10 +58,15 @@ export class ShellNavigationSubItemComponent {
|
||||
});
|
||||
}
|
||||
|
||||
/** The navigation route configuration (static or factory function). */
|
||||
readonly item = input.required<
|
||||
NavigationRouteWithLabel | NavigationRouteWithLabelFn
|
||||
>();
|
||||
|
||||
/**
|
||||
* Resolves the route configuration, handling factory functions and signals.
|
||||
* Returns the current route object with label, path, and query params.
|
||||
*/
|
||||
readonly route = computed(() => {
|
||||
const item = this.item();
|
||||
|
||||
@@ -57,8 +78,13 @@ export class ShellNavigationSubItemComponent {
|
||||
return item;
|
||||
});
|
||||
|
||||
/** Extracts the display label from the resolved route. */
|
||||
readonly label = computed(() => this.route()?.label ?? '');
|
||||
|
||||
/**
|
||||
* Resolves the indicator value from the route configuration.
|
||||
* Handles factory functions and signals for dynamic indicator state.
|
||||
*/
|
||||
readonly resolvedIndicator = computed((): NavigationItemIndicator => {
|
||||
const route = this.route();
|
||||
if (!route) return null;
|
||||
@@ -71,6 +97,7 @@ export class ShellNavigationSubItemComponent {
|
||||
return indicator;
|
||||
});
|
||||
|
||||
/** Closes the navigation sidebar (used on tablet/mobile after selection). */
|
||||
closeNavigation(): void {
|
||||
this.#navigationService.set(false);
|
||||
}
|
||||
|
||||
@@ -26,8 +26,20 @@ import { NavigationGroup, NavigationItem } from './types';
|
||||
import { startOfDay, subDays } from 'date-fns';
|
||||
import { computed, inject } from '@angular/core';
|
||||
|
||||
/** Cached start of day timestamp for date-based query params. */
|
||||
const START_OF_DAY = startOfDay(new Date());
|
||||
|
||||
/**
|
||||
* Main navigation configuration for the application shell.
|
||||
*
|
||||
* Defines the sidebar navigation structure with groups, items, routes,
|
||||
* and activity indicators. Routes can be:
|
||||
* - Static routes (e.g., `/dashboard`)
|
||||
* - Tab-based routes using `injectTabRoute()` or `injectLegacyTabRoute()`
|
||||
* - Factory functions that return signals for reactive route updates
|
||||
*
|
||||
* Indicators show a red dot when items have pending actions (e.g., items in cart).
|
||||
*/
|
||||
export const navigations: Array<NavigationGroup | NavigationItem> = [
|
||||
// Standalone item with dynamic tabId route
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user