@mckit/layout-axis 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # @mckit/layout-axis
2
+
3
+ Axis layout for MC Kit: a dark navigation sidebar, a white topbar with **left / center / right** slots and a light content area. Built for Angular 21, PrimeNG 21 and Tailwind CSS v4.
4
+
5
+ It follows the same structure as `MCFuseLayoutComponent`, so everything you already register through `MCSidebarService`, `MCTopbarService` and `MCFooterService` (menus, logos, tenant switcher, avatar, dark mode button...) keeps working without changes.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @mckit/core @mckit/layout-core @mckit/layout-axis
11
+ ```
12
+
13
+ Tailwind CSS v4 needs to scan the library so its utility classes are generated. Add to your `styles.scss`:
14
+
15
+ ```scss
16
+ @use "tailwindcss";
17
+ @source "../node_modules/@mckit/layout-axis";
18
+
19
+ @custom-variant dark (&:where(.dark, .dark *));
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ Use `MCAxisLayoutComponent` as the parent route of your pages:
25
+
26
+ ```ts
27
+ import { Routes } from '@angular/router';
28
+ import { MCAxisLayoutComponent } from '@mckit/layout-axis';
29
+
30
+ export const routes: Routes = [
31
+ {
32
+ path: '',
33
+ component: MCAxisLayoutComponent,
34
+ children: [
35
+ { path: '', component: DashboardPage },
36
+ { path: 'opportunities', component: OpportunitiesPage },
37
+ ],
38
+ },
39
+ ];
40
+ ```
41
+
42
+ Then fill the slots from your root component, exactly as with the Fuse layout:
43
+
44
+ ```ts
45
+ import { MCAvatar, MCImage, MCMenu, MCSubtitle } from '@mckit/core';
46
+ import { MCIconToggleSidebarButton } from '@mckit/layout';
47
+ import { MCFooterService, MCSidebarService, MCTopbarService } from '@mckit/layout-core';
48
+
49
+ export class AppComponent implements OnInit {
50
+ sidebarService = inject(MCSidebarService);
51
+ topbarService = inject(MCTopbarService);
52
+ footerService = inject(MCFooterService);
53
+
54
+ ngOnInit(): void {
55
+ this.sidebarService.setComponents([
56
+ new MCImage('assets/logo-white.svg', 150),
57
+ new MCSubtitle('Programa'),
58
+ new MCMenu([
59
+ { label: 'Resumen', link: '/', icon: 'pi pi-th-large' },
60
+ { label: 'Oportunidades', link: '/opportunities', icon: 'pi pi-lightbulb' },
61
+ ]),
62
+ ]);
63
+
64
+ this.topbarService.addComponentToLeft(new MCIconToggleSidebarButton());
65
+ this.topbarService.addComponentToCenter(new StageSelector());
66
+ this.topbarService.addComponentToRight(new MCAvatar({ label: 'AM', shape: 'circle' }));
67
+
68
+ this.footerService.addComponent(new MCSubtitle('© 2026'));
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### Slots
74
+
75
+ | Slot | Service method | Notes |
76
+ | :------------ | :--------------------------------------- | :------------------------------------------------- |
77
+ | Sidebar | `MCSidebarService.setComponents()` | Dark surface. Stock `MCMenu` / `MCSubtitle` are re-skinned automatically. |
78
+ | Topbar left | `MCTopbarService.addComponentToLeft()` | Usually the sidebar toggle and the app title. |
79
+ | Topbar center | `MCTopbarService.addComponentToCenter()` | Hidden below the `md` breakpoint. |
80
+ | Topbar right | `MCTopbarService.addComponentToRight()` | Actions, notifications, avatar. |
81
+ | Footer | `MCFooterService.addComponent()` | Rendered below the routed page. |
82
+
83
+ ### Behaviour
84
+
85
+ - The sidebar state lives in `MCSidebarService.isOpen`, so `MCIconToggleSidebarButton` (or any custom button) toggles it.
86
+ - Below `768px` the sidebar becomes an overlay drawer with a backdrop and a close button, and starts closed.
87
+ - A progress spinner overlays the content while a route is being resolved (`NavigationStart` → `NavigationEnd | NavigationCancel | NavigationError`).
88
+ - Dark mode follows the `.dark` class on an ancestor (PrimeNG `darkModeSelector: '.dark'`). The sidebar is always dark.
89
+
90
+ ## Theming
91
+
92
+ Every color and dimension is a CSS custom property set on the `mc-axis-layout` host, so you can override them from a global stylesheet:
93
+
94
+ ```scss
95
+ mc-axis-layout {
96
+ --mc-axis-sidebar-bg: #111827;
97
+ --mc-axis-sidebar-width: 18rem;
98
+ --mc-axis-layout-bg: #fafafa;
99
+ }
100
+ ```
101
+
102
+ | Variable | Default | Description |
103
+ | :------------------------------ | :-------------------------- | :--------------------------------------- |
104
+ | `--mc-axis-sidebar-width` | `17rem` | Sidebar width on desktop. |
105
+ | `--mc-axis-topbar-height` | `4rem` | Topbar height. |
106
+ | `--mc-axis-content-max-width` | `96rem` | Max width of the routed content. |
107
+ | `--mc-axis-transition` | `0.3s ease` | Sidebar open / close transition. |
108
+ | `--mc-axis-layout-bg` | `#f5f6f8` | Content background (light mode). |
109
+ | `--mc-axis-layout-bg-dark` | `#0b1220` | Content background (dark mode). |
110
+ | `--mc-axis-topbar-bg` | `#ffffff` | Topbar background (light mode). |
111
+ | `--mc-axis-topbar-bg-dark` | `#111a2b` | Topbar background (dark mode). |
112
+ | `--mc-axis-border-color` | `#e6e8ee` | Topbar border (light mode). |
113
+ | `--mc-axis-border-color-dark` | `#1f2a3d` | Topbar border (dark mode). |
114
+ | `--mc-axis-sidebar-bg` | `#0f1b2d` | Sidebar background. |
115
+ | `--mc-axis-sidebar-text` | `#e8ecf4` | Sidebar text and icons (active / hover). |
116
+ | `--mc-axis-sidebar-muted` | `#8a95aa` | Section titles and idle icons. |
117
+ | `--mc-axis-sidebar-border` | `rgba(255, 255, 255, 0.08)` | Sidebar right border. |
118
+ | `--mc-axis-sidebar-hover-bg` | `rgba(255, 255, 255, 0.06)` | Menu item hover background. |
119
+ | `--mc-axis-sidebar-active-bg` | `rgba(255, 255, 255, 0.12)` | Active menu item background. |
120
+ | `--mc-axis-sidebar-scrollbar` | `rgba(255, 255, 255, 0.15)` | Sidebar scrollbar thumb. |
121
+
122
+ Structural classes are shared with the Fuse layout (`layout-wrapper`, `layout-sidebar`, `layout-topbar`, `layout-main`, `layout-footer`) and prefixed ones are available for Axis-specific overrides (`mc-axis-sidebar`, `mc-axis-topbar`, `mc-axis-content`...).
123
+
124
+ ## Development
125
+
126
+ ```bash
127
+ ng build @mckit/layout-axis # build to dist/mckit/layout-axis
128
+ ng test @mckit/layout-axis # vitest unit tests
129
+ npm run publish-layout-axis # build + publish to npm
130
+ ```
131
+
132
+ The playground app serves this layout at [http://localhost:4200/axis](http://localhost:4200/axis) (`npm start`).
@@ -0,0 +1,100 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, afterNextRender, HostListener, Component } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import * as i1 from '@angular/router';
5
+ import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError, RouterModule } from '@angular/router';
6
+ import { PrintServiceComponent } from '@mckit/core';
7
+ import { MCSidebarService, ID_SIDEBAR_MC_COMPONENT, ID_TOPBAR_MC_COMPONENT, ID_FOOTER_MC_COMPONENT } from '@mckit/layout-core';
8
+ import * as i2 from 'primeng/button';
9
+ import { ButtonModule } from 'primeng/button';
10
+ import * as i3 from 'primeng/progressspinner';
11
+ import { ProgressSpinnerModule } from 'primeng/progressspinner';
12
+
13
+ /**
14
+ * Viewport width (px) under which the sidebar behaves as an overlay drawer
15
+ * instead of a fixed column. Matches Tailwind's `md` breakpoint.
16
+ */
17
+ const MC_AXIS_MOBILE_BREAKPOINT = 768;
18
+ /**
19
+ * Axis layout: dark navigation sidebar, white topbar with left / center / right
20
+ * slots and a light content area.
21
+ *
22
+ * It follows the same structure as `MCFuseLayoutComponent`, so every MCKit
23
+ * component registered through `MCSidebarService`, `MCTopbarService` and
24
+ * `MCFooterService` keeps working without changes.
25
+ */
26
+ class MCAxisLayoutComponent {
27
+ router = inject(Router);
28
+ sidebarService = inject(MCSidebarService);
29
+ sidebarId = ID_SIDEBAR_MC_COMPONENT;
30
+ topbarLeftId = ID_TOPBAR_MC_COMPONENT + '_left';
31
+ topbarCenterId = ID_TOPBAR_MC_COMPONENT + '_center';
32
+ topbarRightId = ID_TOPBAR_MC_COMPONENT + '_right';
33
+ footerId = ID_FOOTER_MC_COMPONENT;
34
+ isOpen = this.sidebarService.isOpen;
35
+ isRouteLoading = signal(false, ...(ngDevMode ? [{ debugName: "isRouteLoading" }] : /* istanbul ignore next */ []));
36
+ isMobile = signal(false, ...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
37
+ constructor() {
38
+ afterNextRender(() => this.initSidebar());
39
+ this.router.events
40
+ .pipe(takeUntilDestroyed())
41
+ .subscribe((event) => this.onRouterEvent(event));
42
+ }
43
+ onResize() {
44
+ const wasMobile = this.isMobile();
45
+ const isMobileNow = this.verifyIfMobile();
46
+ this.isMobile.set(isMobileNow);
47
+ if (isMobileNow && !wasMobile) {
48
+ this.closeSidebar();
49
+ }
50
+ }
51
+ initSidebar() {
52
+ this.isMobile.set(this.verifyIfMobile());
53
+ if (this.isMobile()) {
54
+ this.closeSidebar();
55
+ }
56
+ }
57
+ verifyIfMobile() {
58
+ return typeof window !== 'undefined' && window.innerWidth < MC_AXIS_MOBILE_BREAKPOINT;
59
+ }
60
+ closeSidebar() {
61
+ this.sidebarService.isOpen.set(false);
62
+ }
63
+ toggleSidebar() {
64
+ this.sidebarService.isOpen.update((isOpen) => !isOpen);
65
+ }
66
+ onRouterEvent(event) {
67
+ if (event instanceof NavigationStart) {
68
+ this.isRouteLoading.set(true);
69
+ return;
70
+ }
71
+ if (event instanceof NavigationEnd ||
72
+ event instanceof NavigationCancel ||
73
+ event instanceof NavigationError) {
74
+ this.isRouteLoading.set(false);
75
+ }
76
+ }
77
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MCAxisLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
78
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: MCAxisLayoutComponent, isStandalone: true, selector: "mc-axis-layout", host: { listeners: { "window:resize": "onResize()" } }, ngImport: i0, template: "<div id=\"mc-axis-layout\" class=\"mc-axis-layout layout-wrapper flex h-screen w-full overflow-hidden\">\n <!-- Sidebar -->\n <aside\n class=\"layout-sidebar mc-axis-sidebar fixed inset-y-0 left-0 z-30 w-full h-full md:relative flex-shrink-0 overflow-y-auto overflow-x-hidden\"\n [class.open]=\"isOpen()\">\n <div class=\"layout-sidebar-close-button absolute right-2 top-2 md:hidden z-10\">\n <p-button icon=\"pi pi-times\" [rounded]=\"true\" [text]=\"true\" severity=\"secondary\" ariaLabel=\"Close menu\"\n (onClick)=\"closeSidebar()\" />\n </div>\n <div class=\"mc-axis-sidebar-content w-full min-h-full flex flex-col\">\n <mc-print-service [id]=\"sidebarId\"></mc-print-service>\n </div>\n </aside>\n <!-- END Sidebar -->\n\n @if (isOpen()) {\n <div class=\"layout-sidebar-overlay md:hidden fixed inset-0 z-20 bg-black/50 transition-opacity\" aria-hidden=\"true\"\n (click)=\"closeSidebar()\"></div>\n }\n\n <div\n class=\"layout-main-container mc-axis-main-container relative flex-1 flex flex-col h-full min-w-0 overflow-y-auto w-full transition-all duration-300\"\n [class.without-sidebar]=\"!isOpen()\">\n <!-- Topbar -->\n <header\n class=\"layout-topbar mc-axis-topbar w-full flex-shrink-0 flex justify-between items-center gap-4 px-4 md:px-6 sticky top-0 z-10\">\n <div class=\"p-toolbar-group-start mc-axis-topbar-start h-full flex items-center min-w-0\">\n <mc-print-service [id]=\"topbarLeftId\" [isFlexRow]=\"true\" [classes]=\"'items-center gap-3'\"></mc-print-service>\n </div>\n <div class=\"p-toolbar-group-center mc-axis-topbar-center h-full hidden md:flex flex-1 items-center justify-center min-w-0\">\n <mc-print-service [id]=\"topbarCenterId\" [isFlexRow]=\"true\" [classes]=\"'items-center gap-3'\"></mc-print-service>\n </div>\n <div class=\"p-toolbar-group-end mc-axis-topbar-end layout-topbar-menu h-full flex items-center\">\n <mc-print-service [id]=\"topbarRightId\" [isFlexRow]=\"true\" [classes]=\"'items-center gap-3'\"></mc-print-service>\n </div>\n </header>\n <!-- END Topbar -->\n\n <main class=\"layout-main mc-axis-main w-full flex-1 relative flex flex-col\" [class.open]=\"isOpen()\">\n @if (isRouteLoading()) {\n <div\n class=\"route-loading-overlay absolute inset-0 z-50 flex items-center justify-center bg-white/50 dark:bg-gray-900/50 backdrop-blur-sm\">\n <p-progressSpinner ariaLabel=\"loading\" strokeWidth=\"4\" />\n </div>\n }\n <div class=\"mc-axis-content w-full mx-auto flex-1 flex flex-col p-4 md:p-6\">\n <router-outlet></router-outlet>\n </div>\n </main>\n\n <!-- Footer -->\n <footer class=\"layout-footer mc-axis-footer w-full flex flex-shrink-0 justify-center mt-auto\">\n <mc-print-service [id]=\"footerId\"></mc-print-service>\n </footer>\n <!-- END Footer -->\n </div>\n</div>\n", styles: [":host{--mc-axis-sidebar-width: 17rem;--mc-axis-topbar-height: 4rem;--mc-axis-content-max-width: 96rem;--mc-axis-transition: .3s ease;--mc-axis-layout-bg: #f5f6f8;--mc-axis-layout-bg-dark: #0b1220;--mc-axis-topbar-bg: #ffffff;--mc-axis-topbar-bg-dark: #111a2b;--mc-axis-border-color: #e6e8ee;--mc-axis-border-color-dark: #1f2a3d;--mc-axis-sidebar-bg: #0f1b2d;--mc-axis-sidebar-text: #e8ecf4;--mc-axis-sidebar-muted: #8a95aa;--mc-axis-sidebar-border: rgba(255, 255, 255, .08);--mc-axis-sidebar-hover-bg: rgba(255, 255, 255, .06);--mc-axis-sidebar-active-bg: rgba(255, 255, 255, .12);--mc-axis-sidebar-scrollbar: rgba(255, 255, 255, .15);display:block;height:100%}.mc-axis-layout{background-color:var(--mc-axis-layout-bg)}:host-context(.dark) .mc-axis-layout{background-color:var(--mc-axis-layout-bg-dark)}.layout-sidebar{background-color:var(--mc-axis-sidebar-bg);color:var(--mc-axis-sidebar-text);border-right:1px solid var(--mc-axis-sidebar-border);transform:translate(-100%);transition:transform var(--mc-axis-transition),margin-left var(--mc-axis-transition);scrollbar-width:thin;scrollbar-color:var(--mc-axis-sidebar-scrollbar) transparent}.layout-sidebar.open{transform:translate(0)}@media(min-width:768px){.layout-sidebar{transform:none;width:var(--mc-axis-sidebar-width);margin-left:calc(var(--mc-axis-sidebar-width) * -1)}.layout-sidebar.open{margin-left:0}}.layout-sidebar ::ng-deep .mc-menu-container{padding:0 .75rem}.layout-sidebar ::ng-deep .mc-menu-container a{display:flex;align-items:center;margin:.125rem 0;padding:.625rem .875rem;border-radius:.75rem;color:var(--mc-axis-sidebar-text);font-weight:500;transition:background-color .15s ease,color .15s ease}.layout-sidebar ::ng-deep .mc-menu-container a .layout-menuitem-icon{color:var(--mc-axis-sidebar-muted);transition:color .15s ease}.layout-sidebar ::ng-deep .mc-menu-container a:hover{background-color:var(--mc-axis-sidebar-hover-bg);color:var(--mc-axis-sidebar-text)}.layout-sidebar ::ng-deep .mc-menu-container a.active-route{background-color:var(--mc-axis-sidebar-active-bg);font-weight:600}.layout-sidebar ::ng-deep .mc-menu-container a:hover .layout-menuitem-icon,.layout-sidebar ::ng-deep .mc-menu-container a.active-route .layout-menuitem-icon{color:var(--mc-axis-sidebar-text)}.layout-sidebar ::ng-deep mc-subtitle h2{color:var(--mc-axis-sidebar-muted);font-size:.6875rem;font-weight:600;letter-spacing:.08em;padding:1.25rem 1.5rem .5rem}.layout-sidebar ::ng-deep .p-button.p-button-text{color:var(--mc-axis-sidebar-text)}.layout-topbar{height:var(--mc-axis-topbar-height);background-color:var(--mc-axis-topbar-bg);border-bottom:1px solid var(--mc-axis-border-color)}:host-context(.dark) .layout-topbar{background-color:var(--mc-axis-topbar-bg-dark);border-color:var(--mc-axis-border-color-dark)}.layout-topbar img{max-height:calc(var(--mc-axis-topbar-height) - 1.5rem)}.mc-axis-content{max-width:var(--mc-axis-content-max-width)}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: PrintServiceComponent, selector: "mc-print-service", inputs: ["id", "isFlexRow", "classes"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i3.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }] });
79
+ }
80
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MCAxisLayoutComponent, decorators: [{
81
+ type: Component,
82
+ args: [{ selector: 'mc-axis-layout', imports: [RouterModule, PrintServiceComponent, ButtonModule, ProgressSpinnerModule], template: "<div id=\"mc-axis-layout\" class=\"mc-axis-layout layout-wrapper flex h-screen w-full overflow-hidden\">\n <!-- Sidebar -->\n <aside\n class=\"layout-sidebar mc-axis-sidebar fixed inset-y-0 left-0 z-30 w-full h-full md:relative flex-shrink-0 overflow-y-auto overflow-x-hidden\"\n [class.open]=\"isOpen()\">\n <div class=\"layout-sidebar-close-button absolute right-2 top-2 md:hidden z-10\">\n <p-button icon=\"pi pi-times\" [rounded]=\"true\" [text]=\"true\" severity=\"secondary\" ariaLabel=\"Close menu\"\n (onClick)=\"closeSidebar()\" />\n </div>\n <div class=\"mc-axis-sidebar-content w-full min-h-full flex flex-col\">\n <mc-print-service [id]=\"sidebarId\"></mc-print-service>\n </div>\n </aside>\n <!-- END Sidebar -->\n\n @if (isOpen()) {\n <div class=\"layout-sidebar-overlay md:hidden fixed inset-0 z-20 bg-black/50 transition-opacity\" aria-hidden=\"true\"\n (click)=\"closeSidebar()\"></div>\n }\n\n <div\n class=\"layout-main-container mc-axis-main-container relative flex-1 flex flex-col h-full min-w-0 overflow-y-auto w-full transition-all duration-300\"\n [class.without-sidebar]=\"!isOpen()\">\n <!-- Topbar -->\n <header\n class=\"layout-topbar mc-axis-topbar w-full flex-shrink-0 flex justify-between items-center gap-4 px-4 md:px-6 sticky top-0 z-10\">\n <div class=\"p-toolbar-group-start mc-axis-topbar-start h-full flex items-center min-w-0\">\n <mc-print-service [id]=\"topbarLeftId\" [isFlexRow]=\"true\" [classes]=\"'items-center gap-3'\"></mc-print-service>\n </div>\n <div class=\"p-toolbar-group-center mc-axis-topbar-center h-full hidden md:flex flex-1 items-center justify-center min-w-0\">\n <mc-print-service [id]=\"topbarCenterId\" [isFlexRow]=\"true\" [classes]=\"'items-center gap-3'\"></mc-print-service>\n </div>\n <div class=\"p-toolbar-group-end mc-axis-topbar-end layout-topbar-menu h-full flex items-center\">\n <mc-print-service [id]=\"topbarRightId\" [isFlexRow]=\"true\" [classes]=\"'items-center gap-3'\"></mc-print-service>\n </div>\n </header>\n <!-- END Topbar -->\n\n <main class=\"layout-main mc-axis-main w-full flex-1 relative flex flex-col\" [class.open]=\"isOpen()\">\n @if (isRouteLoading()) {\n <div\n class=\"route-loading-overlay absolute inset-0 z-50 flex items-center justify-center bg-white/50 dark:bg-gray-900/50 backdrop-blur-sm\">\n <p-progressSpinner ariaLabel=\"loading\" strokeWidth=\"4\" />\n </div>\n }\n <div class=\"mc-axis-content w-full mx-auto flex-1 flex flex-col p-4 md:p-6\">\n <router-outlet></router-outlet>\n </div>\n </main>\n\n <!-- Footer -->\n <footer class=\"layout-footer mc-axis-footer w-full flex flex-shrink-0 justify-center mt-auto\">\n <mc-print-service [id]=\"footerId\"></mc-print-service>\n </footer>\n <!-- END Footer -->\n </div>\n</div>\n", styles: [":host{--mc-axis-sidebar-width: 17rem;--mc-axis-topbar-height: 4rem;--mc-axis-content-max-width: 96rem;--mc-axis-transition: .3s ease;--mc-axis-layout-bg: #f5f6f8;--mc-axis-layout-bg-dark: #0b1220;--mc-axis-topbar-bg: #ffffff;--mc-axis-topbar-bg-dark: #111a2b;--mc-axis-border-color: #e6e8ee;--mc-axis-border-color-dark: #1f2a3d;--mc-axis-sidebar-bg: #0f1b2d;--mc-axis-sidebar-text: #e8ecf4;--mc-axis-sidebar-muted: #8a95aa;--mc-axis-sidebar-border: rgba(255, 255, 255, .08);--mc-axis-sidebar-hover-bg: rgba(255, 255, 255, .06);--mc-axis-sidebar-active-bg: rgba(255, 255, 255, .12);--mc-axis-sidebar-scrollbar: rgba(255, 255, 255, .15);display:block;height:100%}.mc-axis-layout{background-color:var(--mc-axis-layout-bg)}:host-context(.dark) .mc-axis-layout{background-color:var(--mc-axis-layout-bg-dark)}.layout-sidebar{background-color:var(--mc-axis-sidebar-bg);color:var(--mc-axis-sidebar-text);border-right:1px solid var(--mc-axis-sidebar-border);transform:translate(-100%);transition:transform var(--mc-axis-transition),margin-left var(--mc-axis-transition);scrollbar-width:thin;scrollbar-color:var(--mc-axis-sidebar-scrollbar) transparent}.layout-sidebar.open{transform:translate(0)}@media(min-width:768px){.layout-sidebar{transform:none;width:var(--mc-axis-sidebar-width);margin-left:calc(var(--mc-axis-sidebar-width) * -1)}.layout-sidebar.open{margin-left:0}}.layout-sidebar ::ng-deep .mc-menu-container{padding:0 .75rem}.layout-sidebar ::ng-deep .mc-menu-container a{display:flex;align-items:center;margin:.125rem 0;padding:.625rem .875rem;border-radius:.75rem;color:var(--mc-axis-sidebar-text);font-weight:500;transition:background-color .15s ease,color .15s ease}.layout-sidebar ::ng-deep .mc-menu-container a .layout-menuitem-icon{color:var(--mc-axis-sidebar-muted);transition:color .15s ease}.layout-sidebar ::ng-deep .mc-menu-container a:hover{background-color:var(--mc-axis-sidebar-hover-bg);color:var(--mc-axis-sidebar-text)}.layout-sidebar ::ng-deep .mc-menu-container a.active-route{background-color:var(--mc-axis-sidebar-active-bg);font-weight:600}.layout-sidebar ::ng-deep .mc-menu-container a:hover .layout-menuitem-icon,.layout-sidebar ::ng-deep .mc-menu-container a.active-route .layout-menuitem-icon{color:var(--mc-axis-sidebar-text)}.layout-sidebar ::ng-deep mc-subtitle h2{color:var(--mc-axis-sidebar-muted);font-size:.6875rem;font-weight:600;letter-spacing:.08em;padding:1.25rem 1.5rem .5rem}.layout-sidebar ::ng-deep .p-button.p-button-text{color:var(--mc-axis-sidebar-text)}.layout-topbar{height:var(--mc-axis-topbar-height);background-color:var(--mc-axis-topbar-bg);border-bottom:1px solid var(--mc-axis-border-color)}:host-context(.dark) .layout-topbar{background-color:var(--mc-axis-topbar-bg-dark);border-color:var(--mc-axis-border-color-dark)}.layout-topbar img{max-height:calc(var(--mc-axis-topbar-height) - 1.5rem)}.mc-axis-content{max-width:var(--mc-axis-content-max-width)}\n"] }]
83
+ }], ctorParameters: () => [], propDecorators: { onResize: [{
84
+ type: HostListener,
85
+ args: ['window:resize']
86
+ }] } });
87
+
88
+ /*
89
+ * Public API Surface of layout-axis
90
+ */
91
+ /**
92
+ * Layouts
93
+ */
94
+
95
+ /**
96
+ * Generated bundle index. Do not edit.
97
+ */
98
+
99
+ export { MCAxisLayoutComponent, MC_AXIS_MOBILE_BREAKPOINT };
100
+ //# sourceMappingURL=mckit-layout-axis.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mckit-layout-axis.mjs","sources":["../../../../projects/mckit/layout-axis/src/lib/layouts/axis-layout/axis-layout.component.ts","../../../../projects/mckit/layout-axis/src/lib/layouts/axis-layout/axis-layout.component.html","../../../../projects/mckit/layout-axis/src/public-api.ts","../../../../projects/mckit/layout-axis/src/mckit-layout-axis.ts"],"sourcesContent":["import { afterNextRender, Component, HostListener, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n Event as RouterEvent,\n NavigationCancel,\n NavigationEnd,\n NavigationError,\n NavigationStart,\n Router,\n RouterModule,\n} from '@angular/router';\nimport { PrintServiceComponent } from '@mckit/core';\nimport {\n ID_FOOTER_MC_COMPONENT,\n ID_SIDEBAR_MC_COMPONENT,\n ID_TOPBAR_MC_COMPONENT,\n MCSidebarService,\n} from '@mckit/layout-core';\nimport { ButtonModule } from 'primeng/button';\nimport { ProgressSpinnerModule } from 'primeng/progressspinner';\n\n/**\n * Viewport width (px) under which the sidebar behaves as an overlay drawer\n * instead of a fixed column. Matches Tailwind's `md` breakpoint.\n */\nexport const MC_AXIS_MOBILE_BREAKPOINT = 768;\n\n/**\n * Axis layout: dark navigation sidebar, white topbar with left / center / right\n * slots and a light content area.\n *\n * It follows the same structure as `MCFuseLayoutComponent`, so every MCKit\n * component registered through `MCSidebarService`, `MCTopbarService` and\n * `MCFooterService` keeps working without changes.\n */\n@Component({\n selector: 'mc-axis-layout',\n imports: [RouterModule, PrintServiceComponent, ButtonModule, ProgressSpinnerModule],\n templateUrl: './axis-layout.component.html',\n styleUrl: './axis-layout.component.scss',\n})\nexport class MCAxisLayoutComponent {\n router: Router = inject(Router);\n sidebarService: MCSidebarService = inject(MCSidebarService);\n\n sidebarId = ID_SIDEBAR_MC_COMPONENT;\n topbarLeftId = ID_TOPBAR_MC_COMPONENT + '_left';\n topbarCenterId = ID_TOPBAR_MC_COMPONENT + '_center';\n topbarRightId = ID_TOPBAR_MC_COMPONENT + '_right';\n footerId = ID_FOOTER_MC_COMPONENT;\n\n isOpen = this.sidebarService.isOpen;\n isRouteLoading = signal<boolean>(false);\n isMobile = signal<boolean>(false);\n\n constructor() {\n afterNextRender(() => this.initSidebar());\n\n this.router.events\n .pipe(takeUntilDestroyed())\n .subscribe((event) => this.onRouterEvent(event));\n }\n\n @HostListener('window:resize')\n onResize(): void {\n const wasMobile = this.isMobile();\n const isMobileNow = this.verifyIfMobile();\n this.isMobile.set(isMobileNow);\n\n if (isMobileNow && !wasMobile) {\n this.closeSidebar();\n }\n }\n\n initSidebar(): void {\n this.isMobile.set(this.verifyIfMobile());\n\n if (this.isMobile()) {\n this.closeSidebar();\n }\n }\n\n verifyIfMobile(): boolean {\n return typeof window !== 'undefined' && window.innerWidth < MC_AXIS_MOBILE_BREAKPOINT;\n }\n\n closeSidebar(): void {\n this.sidebarService.isOpen.set(false);\n }\n\n toggleSidebar(): void {\n this.sidebarService.isOpen.update((isOpen) => !isOpen);\n }\n\n protected onRouterEvent(event: RouterEvent): void {\n if (event instanceof NavigationStart) {\n this.isRouteLoading.set(true);\n return;\n }\n\n if (\n event instanceof NavigationEnd ||\n event instanceof NavigationCancel ||\n event instanceof NavigationError\n ) {\n this.isRouteLoading.set(false);\n }\n }\n}\n","<div id=\"mc-axis-layout\" class=\"mc-axis-layout layout-wrapper flex h-screen w-full overflow-hidden\">\n <!-- Sidebar -->\n <aside\n class=\"layout-sidebar mc-axis-sidebar fixed inset-y-0 left-0 z-30 w-full h-full md:relative flex-shrink-0 overflow-y-auto overflow-x-hidden\"\n [class.open]=\"isOpen()\">\n <div class=\"layout-sidebar-close-button absolute right-2 top-2 md:hidden z-10\">\n <p-button icon=\"pi pi-times\" [rounded]=\"true\" [text]=\"true\" severity=\"secondary\" ariaLabel=\"Close menu\"\n (onClick)=\"closeSidebar()\" />\n </div>\n <div class=\"mc-axis-sidebar-content w-full min-h-full flex flex-col\">\n <mc-print-service [id]=\"sidebarId\"></mc-print-service>\n </div>\n </aside>\n <!-- END Sidebar -->\n\n @if (isOpen()) {\n <div class=\"layout-sidebar-overlay md:hidden fixed inset-0 z-20 bg-black/50 transition-opacity\" aria-hidden=\"true\"\n (click)=\"closeSidebar()\"></div>\n }\n\n <div\n class=\"layout-main-container mc-axis-main-container relative flex-1 flex flex-col h-full min-w-0 overflow-y-auto w-full transition-all duration-300\"\n [class.without-sidebar]=\"!isOpen()\">\n <!-- Topbar -->\n <header\n class=\"layout-topbar mc-axis-topbar w-full flex-shrink-0 flex justify-between items-center gap-4 px-4 md:px-6 sticky top-0 z-10\">\n <div class=\"p-toolbar-group-start mc-axis-topbar-start h-full flex items-center min-w-0\">\n <mc-print-service [id]=\"topbarLeftId\" [isFlexRow]=\"true\" [classes]=\"'items-center gap-3'\"></mc-print-service>\n </div>\n <div class=\"p-toolbar-group-center mc-axis-topbar-center h-full hidden md:flex flex-1 items-center justify-center min-w-0\">\n <mc-print-service [id]=\"topbarCenterId\" [isFlexRow]=\"true\" [classes]=\"'items-center gap-3'\"></mc-print-service>\n </div>\n <div class=\"p-toolbar-group-end mc-axis-topbar-end layout-topbar-menu h-full flex items-center\">\n <mc-print-service [id]=\"topbarRightId\" [isFlexRow]=\"true\" [classes]=\"'items-center gap-3'\"></mc-print-service>\n </div>\n </header>\n <!-- END Topbar -->\n\n <main class=\"layout-main mc-axis-main w-full flex-1 relative flex flex-col\" [class.open]=\"isOpen()\">\n @if (isRouteLoading()) {\n <div\n class=\"route-loading-overlay absolute inset-0 z-50 flex items-center justify-center bg-white/50 dark:bg-gray-900/50 backdrop-blur-sm\">\n <p-progressSpinner ariaLabel=\"loading\" strokeWidth=\"4\" />\n </div>\n }\n <div class=\"mc-axis-content w-full mx-auto flex-1 flex flex-col p-4 md:p-6\">\n <router-outlet></router-outlet>\n </div>\n </main>\n\n <!-- Footer -->\n <footer class=\"layout-footer mc-axis-footer w-full flex flex-shrink-0 justify-center mt-auto\">\n <mc-print-service [id]=\"footerId\"></mc-print-service>\n </footer>\n <!-- END Footer -->\n </div>\n</div>\n","/*\n * Public API Surface of layout-axis\n */\n\n/**\n * Layouts\n */\nexport * from \"./lib/layouts/axis-layout/axis-layout.component\";\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@mckit/layout-axis",
3
+ "version": "21.0.0",
4
+ "peerDependencies": {
5
+ "@angular/common": "^21.2.0",
6
+ "@angular/core": "^21.2.0",
7
+ "@mckit/core": "^21.0.0",
8
+ "@mckit/layout-core": "^21.0.0",
9
+ "primeng": "^21.0.0"
10
+ },
11
+ "dependencies": {
12
+ "tslib": "^2.3.0"
13
+ },
14
+ "sideEffects": false,
15
+ "module": "fesm2022/mckit-layout-axis.mjs",
16
+ "typings": "types/mckit-layout-axis.d.ts",
17
+ "exports": {
18
+ "./package.json": {
19
+ "default": "./package.json"
20
+ },
21
+ ".": {
22
+ "types": "./types/mckit-layout-axis.d.ts",
23
+ "default": "./fesm2022/mckit-layout-axis.mjs"
24
+ }
25
+ },
26
+ "type": "module"
27
+ }
@@ -0,0 +1,40 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { Router, Event } from '@angular/router';
3
+ import { MCSidebarService } from '@mckit/layout-core';
4
+
5
+ /**
6
+ * Viewport width (px) under which the sidebar behaves as an overlay drawer
7
+ * instead of a fixed column. Matches Tailwind's `md` breakpoint.
8
+ */
9
+ declare const MC_AXIS_MOBILE_BREAKPOINT = 768;
10
+ /**
11
+ * Axis layout: dark navigation sidebar, white topbar with left / center / right
12
+ * slots and a light content area.
13
+ *
14
+ * It follows the same structure as `MCFuseLayoutComponent`, so every MCKit
15
+ * component registered through `MCSidebarService`, `MCTopbarService` and
16
+ * `MCFooterService` keeps working without changes.
17
+ */
18
+ declare class MCAxisLayoutComponent {
19
+ router: Router;
20
+ sidebarService: MCSidebarService;
21
+ sidebarId: string;
22
+ topbarLeftId: string;
23
+ topbarCenterId: string;
24
+ topbarRightId: string;
25
+ footerId: string;
26
+ isOpen: _angular_core.WritableSignal<boolean>;
27
+ isRouteLoading: _angular_core.WritableSignal<boolean>;
28
+ isMobile: _angular_core.WritableSignal<boolean>;
29
+ constructor();
30
+ onResize(): void;
31
+ initSidebar(): void;
32
+ verifyIfMobile(): boolean;
33
+ closeSidebar(): void;
34
+ toggleSidebar(): void;
35
+ protected onRouterEvent(event: Event): void;
36
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MCAxisLayoutComponent, never>;
37
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MCAxisLayoutComponent, "mc-axis-layout", never, {}, {}, never, never, true, never>;
38
+ }
39
+
40
+ export { MCAxisLayoutComponent, MC_AXIS_MOBILE_BREAKPOINT };