@softwarity/rail-nav 1.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 softwarity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,276 @@
1
+ <p align="center">
2
+ <a href="https://www.softwarity.io/">
3
+ <img src="https://www.softwarity.io/img/softwarity.svg" alt="Softwarity" height="60">
4
+ </a>
5
+ </p>
6
+
7
+ # @softwarity/rail-nav
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@softwarity/rail-nav">
11
+ <img src="https://img.shields.io/npm/v/@softwarity/rail-nav?color=blue&label=npm" alt="npm version">
12
+ </a>
13
+ <a href="https://github.com/softwarity/rail-nav/blob/main/LICENSE">
14
+ <img src="https://img.shields.io/badge/license-MIT-blue" alt="license">
15
+ </a>
16
+ </p>
17
+
18
+ Material Design 3 Navigation Rail component for Angular. Extends Angular Material `mat-sidenav` to provide a collapsible rail navigation pattern.
19
+
20
+ **[Live Demo](https://softwarity.github.io/rail-nav/)**
21
+
22
+ ## Features
23
+
24
+ - **Material Design 3** - Implements the Navigation Rail pattern from MD3
25
+ - **Extends Material** - Built on top of `MatSidenav` for reliability
26
+ - **Navigation Items** - `rail-nav-item` component with MD3 pill animation, badges, and router support
27
+ - **Expand/Collapse** - Smooth transition between rail and drawer with adaptive width
28
+ - **Backdrop** - Optional backdrop overlay when expanded
29
+ - **Position** - Support for left (start) or right (end) positioning
30
+ - **Dark Mode** - Automatically adapts to light/dark color scheme
31
+ - **SCSS Theming** - Customizable via SCSS mixin following Angular Material pattern
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ npm install @softwarity/rail-nav
37
+ ```
38
+
39
+ ### Peer Dependencies
40
+
41
+ | Package | Version |
42
+ |---------|---------|
43
+ | @angular/common | >= 21.0.0 |
44
+ | @angular/core | >= 21.0.0 |
45
+ | @angular/cdk | >= 21.0.0 |
46
+ | @angular/material | >= 21.0.0 |
47
+
48
+ ## Usage
49
+
50
+ Import the components in your Angular component:
51
+
52
+ ```typescript
53
+ import {
54
+ RailnavComponent,
55
+ RailnavContainerComponent,
56
+ RailnavContentComponent,
57
+ RailnavItemComponent
58
+ } from '@softwarity/rail-nav';
59
+
60
+ @Component({
61
+ imports: [
62
+ RailnavComponent,
63
+ RailnavContainerComponent,
64
+ RailnavContentComponent,
65
+ RailnavItemComponent
66
+ ],
67
+ template: `
68
+ <rail-nav-container>
69
+ <rail-nav title="My App" subtitle="v1.0">
70
+ <rail-nav-item label="Home" routerLink="/home">
71
+ <mat-icon>home</mat-icon>
72
+ </rail-nav-item>
73
+ <rail-nav-item label="Settings" routerLink="/settings">
74
+ <mat-icon>settings</mat-icon>
75
+ </rail-nav-item>
76
+ </rail-nav>
77
+ <rail-nav-content>
78
+ <!-- Page content -->
79
+ </rail-nav-content>
80
+ </rail-nav-container>
81
+ `
82
+ })
83
+ export class AppComponent {}
84
+ ```
85
+
86
+ ## API
87
+
88
+ ### RailnavComponent
89
+
90
+ The main navigation rail component. Extends `MatSidenav`.
91
+
92
+ | Input | Type | Default | Description |
93
+ |-------|------|---------|-------------|
94
+ | `collapsedWidth` | `number` | `72` | Width in pixels when collapsed (icon-only mode) |
95
+ | `expandedWidth` | `number \| 'auto'` | `'auto'` | Width when expanded. Use `'auto'` for content-based width |
96
+ | `position` | `'start' \| 'end'` | `'start'` | Position of the rail (left or right) |
97
+ | `title` | `string` | - | Title displayed next to burger when expanded |
98
+ | `subtitle` | `string` | - | Subtitle displayed below title when expanded |
99
+ | `headerHeight` | `number` | `56` | Header height in pixels (to match toolbar) |
100
+ | `hideDefaultHeader` | `boolean` | `false` | Hide the default header to provide custom content |
101
+
102
+ | Property/Method | Type | Description |
103
+ |-----------------|------|-------------|
104
+ | `expanded` | `Signal<boolean>` | Read the current expanded state |
105
+ | `toggleExpanded()` | `void` | Toggle between collapsed and expanded |
106
+ | `expand()` | `void` | Expand the rail |
107
+ | `collapse()` | `void` | Collapse the rail |
108
+
109
+ ### RailnavContainerComponent
110
+
111
+ Container component. Extends `MatSidenavContainer`.
112
+
113
+ | Input | Type | Default | Description |
114
+ |-------|------|---------|-------------|
115
+ | `showBackdrop` | `boolean` | `true` | Show backdrop overlay when expanded |
116
+
117
+ ### RailnavContentComponent
118
+
119
+ Content area component. Extends `MatSidenavContent`.
120
+
121
+ | Input | Type | Default | Description |
122
+ |-------|------|---------|-------------|
123
+ | `collapsedWidth` | `number` | `auto` | Width of collapsed rail. Auto-detected from sibling `rail-nav` if not set |
124
+ | `position` | `'start' \| 'end'` | `auto` | Position of rail. Auto-detected from sibling `rail-nav` if not set |
125
+
126
+ **Note:** Both inputs are optional. When placed inside a `rail-nav-container` alongside a `rail-nav`, the component automatically reads `collapsedWidth` and `position` from the sibling rail.
127
+
128
+ ### RailnavItemComponent
129
+
130
+ Navigation item with MD3 pill animation.
131
+
132
+ | Input | Type | Default | Description |
133
+ |-------|------|---------|-------------|
134
+ | `routerLink` | `string \| any[]` | - | Router link for navigation |
135
+ | `label` | `string` | - | Label text (below icon when collapsed, beside when expanded) |
136
+ | `badge` | `string \| number \| boolean` | - | Badge value. Use `true` for a small dot badge |
137
+ | `active` | `boolean` | `false` | Whether this item is active (for non-router usage) |
138
+
139
+ | Output | Type | Description |
140
+ |--------|------|-------------|
141
+ | `itemClick` | `void` | Emitted when clicked (automatically collapses rail) |
142
+
143
+ ## SCSS Theming
144
+
145
+ Use the SCSS mixin to customize colors following the Angular Material pattern:
146
+
147
+ ```scss
148
+ @use '@softwarity/rail-nav' as rail-nav;
149
+
150
+ :root {
151
+ @include rail-nav.overrides((
152
+ backdrop-color: rgba(0, 0, 0, 0.5),
153
+ primary: #6200ea,
154
+ surface-color: #f5f5f5,
155
+ ));
156
+ }
157
+ ```
158
+
159
+ ### Available Tokens
160
+
161
+ | Token | Description |
162
+ |-------|-------------|
163
+ | `backdrop-color` | Background color for the backdrop overlay |
164
+ | `surface-color` | Background color for the rail |
165
+ | `surface-container-high` | Background color for hover states |
166
+ | `on-surface` | Text color for primary content |
167
+ | `on-surface-variant` | Text color for secondary content |
168
+ | `secondary-container` | Background color for active items |
169
+ | `on-secondary-container` | Text color for active items |
170
+ | `primary` | Focus ring color |
171
+ | `error` | Badge background color |
172
+ | `on-error` | Badge text color |
173
+
174
+ ## CSS Custom Properties
175
+
176
+ You can also use CSS custom properties directly:
177
+
178
+ ```css
179
+ :root {
180
+ --rail-nav-backdrop-color: rgba(0, 0, 0, 0.5);
181
+ --rail-nav-surface-color: #ffffff;
182
+ --rail-nav-on-surface: #1c1b1f;
183
+ --rail-nav-primary: #6750a4;
184
+ --rail-nav-error: #b3261e;
185
+ }
186
+ ```
187
+
188
+ The component uses Material Design 3 system tokens (`--mat-sys-*`) as fallbacks.
189
+
190
+ ## Light/Dark Theme Support
191
+
192
+ Use the CSS `light-dark()` function to define colors that automatically adapt to the user's color scheme:
193
+
194
+ ### With SCSS mixin
195
+
196
+ ```scss
197
+ @use '@softwarity/rail-nav' as rail-nav;
198
+
199
+ :root {
200
+ @include rail-nav.overrides((
201
+ backdrop-color: light-dark(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.6)),
202
+ surface-color: light-dark(#ffffff, #1e1e1e),
203
+ on-surface: light-dark(#1c1b1f, #e6e1e5),
204
+ primary: light-dark(#6750a4, #d0bcff),
205
+ ));
206
+ }
207
+ ```
208
+
209
+ ### With CSS custom properties
210
+
211
+ ```css
212
+ :root {
213
+ --rail-nav-backdrop-color: light-dark(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.6));
214
+ --rail-nav-surface-color: light-dark(#ffffff, #1e1e1e);
215
+ --rail-nav-on-surface: light-dark(#1c1b1f, #e6e1e5);
216
+ --rail-nav-on-surface-variant: light-dark(#49454f, #cac4d0);
217
+ --rail-nav-secondary-container: light-dark(#e8def8, #4a4458);
218
+ --rail-nav-on-secondary-container: light-dark(#1d192b, #e8def8);
219
+ --rail-nav-primary: light-dark(#6750a4, #d0bcff);
220
+ --rail-nav-error: light-dark(#b3261e, #f2b8b5);
221
+ --rail-nav-on-error: light-dark(#ffffff, #601410);
222
+ }
223
+ ```
224
+
225
+ ### Setting up color-scheme
226
+
227
+ For `light-dark()` to work, set the `color-scheme` property on your document:
228
+
229
+ ```scss
230
+ html {
231
+ color-scheme: light;
232
+ &.dark-mode { color-scheme: dark; }
233
+ }
234
+
235
+ /* Or use system preference */
236
+ html {
237
+ color-scheme: light dark;
238
+ }
239
+ ```
240
+
241
+ ## Example
242
+
243
+ ```typescript
244
+ @Component({
245
+ imports: [
246
+ RailnavComponent,
247
+ RailnavContainerComponent,
248
+ RailnavContentComponent,
249
+ RailnavItemComponent,
250
+ MatIconModule
251
+ ],
252
+ template: `
253
+ <rail-nav-container [showBackdrop]="true">
254
+ <rail-nav title="My App" subtitle="v1.0">
255
+ <rail-nav-item label="Home" [badge]="3" routerLink="/home">
256
+ <mat-icon>home</mat-icon>
257
+ </rail-nav-item>
258
+ <rail-nav-item label="Favorites" [badge]="true" routerLink="/favorites">
259
+ <mat-icon>favorite</mat-icon>
260
+ </rail-nav-item>
261
+ <rail-nav-item label="Settings" routerLink="/settings">
262
+ <mat-icon>settings</mat-icon>
263
+ </rail-nav-item>
264
+ </rail-nav>
265
+ <rail-nav-content>
266
+ <main>Your content here</main>
267
+ </rail-nav-content>
268
+ </rail-nav-container>
269
+ `
270
+ })
271
+ export class MyComponent {}
272
+ ```
273
+
274
+ ## License
275
+
276
+ MIT
@@ -0,0 +1,356 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, signal, effect, Component, ContentChild, inject, computed, output, ChangeDetectionStrategy } from '@angular/core';
3
+ import { MatSidenav, MatSidenavContainer, MatDrawerContainer, MatSidenavContent } from '@angular/material/sidenav';
4
+ import * as i1 from '@angular/material/core';
5
+ import { MatRippleModule } from '@angular/material/core';
6
+ import { RouterLink, RouterLinkActive } from '@angular/router';
7
+
8
+ class RailnavComponent extends MatSidenav {
9
+ constructor() {
10
+ super();
11
+ /** Position: 'start' (left) or 'end' (right) - aliased to avoid conflict with MatSidenav.position */
12
+ this.railPosition = input('start', { ...(ngDevMode ? { debugName: "railPosition" } : {}), alias: 'position' });
13
+ /** Hide the default header (burger + title/subtitle) */
14
+ this.hideDefaultHeader = input(false, ...(ngDevMode ? [{ debugName: "hideDefaultHeader" }] : []));
15
+ /** Title displayed when expanded */
16
+ this.title = input(...(ngDevMode ? [undefined, { debugName: "title" }] : []));
17
+ /** Subtitle displayed when expanded */
18
+ this.subtitle = input(...(ngDevMode ? [undefined, { debugName: "subtitle" }] : []));
19
+ /** Whether to auto-collapse when an item is clicked */
20
+ this.autoCollapse = input(true, ...(ngDevMode ? [{ debugName: "autoCollapse" }] : []));
21
+ /** Whether the rail is expanded to show labels */
22
+ this.expanded = signal(false, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
23
+ // Default settings for rail behavior
24
+ this.opened = true;
25
+ this.disableClose = true;
26
+ this.mode = 'side';
27
+ // Sync mode with expanded state
28
+ effect(() => {
29
+ this.mode = this.expanded() ? 'over' : 'side';
30
+ });
31
+ }
32
+ /** Toggle between collapsed (rail) and expanded (drawer) */
33
+ toggleExpanded() {
34
+ this.expanded.update(e => !e);
35
+ }
36
+ /** Collapse the rail */
37
+ collapse() {
38
+ this.expanded.set(false);
39
+ }
40
+ /** Expand the rail to drawer */
41
+ expand() {
42
+ this.expanded.set(true);
43
+ }
44
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
45
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: RailnavComponent, isStandalone: true, selector: "rail-nav", inputs: { railPosition: { classPropertyName: "railPosition", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, hideDefaultHeader: { classPropertyName: "hideDefaultHeader", publicName: "hideDefaultHeader", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, autoCollapse: { classPropertyName: "autoCollapse", publicName: "autoCollapse", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.expanded": "expanded()", "class.position-end": "railPosition() === \"end\"" }, classAttribute: "mat-drawer mat-sidenav" }, usesInheritance: true, ngImport: i0, template: `
46
+ @if (!hideDefaultHeader()) {
47
+ <div class="rail-header" [class.position-end]="railPosition() === 'end'" matRipple (click)="toggleExpanded()">
48
+ @if (expanded() && (title() || subtitle()) && railPosition() === 'end') {
49
+ <div class="rail-branding position-end">
50
+ @if (title()) {
51
+ <span class="rail-title">{{ title() }}</span>
52
+ }
53
+ @if (subtitle()) {
54
+ <span class="rail-subtitle">{{ subtitle() }}</span>
55
+ }
56
+ </div>
57
+ }
58
+ <div class="rail-burger" [class.expanded]="expanded()" [class.position-end]="railPosition() === 'end'">
59
+ <!-- Menu icon (collapsed state) -->
60
+ <svg class="icon-menu" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor">
61
+ <path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z"/>
62
+ </svg>
63
+ <!-- Menu open icon (expanded state) -->
64
+ <svg class="icon-menu-open" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor">
65
+ <path d="M120-240v-80h520v80H120Zm664-40L584-480l200-200 56 56-144 144 144 144-56 56ZM120-440v-80h400v80H120Zm0-200v-80h520v80H120Z"/>
66
+ </svg>
67
+ </div>
68
+ @if (expanded() && (title() || subtitle()) && railPosition() === 'start') {
69
+ <div class="rail-branding">
70
+ @if (title()) {
71
+ <span class="rail-title">{{ title() }}</span>
72
+ }
73
+ @if (subtitle()) {
74
+ <span class="rail-subtitle">{{ subtitle() }}</span>
75
+ }
76
+ </div>
77
+ }
78
+ </div>
79
+ }
80
+ <ng-content />
81
+ `, isInline: true, styles: [":host{display:block;position:absolute;top:0;bottom:0;left:0;z-index:100;width:var(--rail-nav-collapsed-width, 72px);border:none!important;border-right:none!important;border-left:none!important;outline:none!important;box-shadow:none;background:var(--rail-nav-surface-color, var(--mat-sys-surface));transition:width .2s ease;overflow:visible}:host(.expanded){width:var(--rail-nav-expanded-width, fit-content);box-shadow:4px 0 8px #0003}:host(.position-end){left:auto;right:0}:host(.position-end.expanded){box-shadow:-4px 0 8px #0003}.rail-header{display:flex;align-items:center;gap:12px;height:var(--rail-nav-header-height, 56px);padding:16px 16px 16px 24px;box-sizing:border-box;cursor:pointer;color:var(--rail-nav-on-surface, var(--mat-sys-on-surface))}.rail-header.position-end{padding:16px 24px 16px 16px}.rail-header.position-end .rail-burger{margin-left:auto}.rail-header:hover{background:var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high))}.rail-burger{position:relative;width:24px;height:24px;flex-shrink:0}.rail-burger.position-end{transform:scaleX(-1)}.rail-burger svg{position:absolute;top:0;left:0;transition:opacity .3s ease,transform .3s ease}.rail-burger .icon-menu{opacity:1;transform:rotate(0)}.rail-burger .icon-menu-open{opacity:0;transform:rotate(-90deg)}.rail-burger.expanded .icon-menu{opacity:0;transform:rotate(90deg)}.rail-burger.expanded .icon-menu-open{opacity:1;transform:rotate(0)}.rail-burger.position-end .icon-menu{transform:rotate(0)}.rail-burger.position-end .icon-menu-open{transform:rotate(90deg)}.rail-burger.position-end.expanded .icon-menu{transform:rotate(-90deg)}.rail-burger.position-end.expanded .icon-menu-open{transform:rotate(0)}.rail-branding{display:flex;flex-direction:column;justify-content:center;gap:0;min-width:0;overflow:hidden;text-align:right;padding-top:5px}.rail-branding.position-end{text-align:left}.rail-title{font-size:16px;font-weight:500;line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--rail-nav-on-surface, var(--mat-sys-on-surface))}.rail-subtitle{font-size:11px;line-height:1;color:var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:.7}\n"], dependencies: [{ kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }] }); }
82
+ }
83
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavComponent, decorators: [{
84
+ type: Component,
85
+ args: [{ selector: 'rail-nav', imports: [MatRippleModule], template: `
86
+ @if (!hideDefaultHeader()) {
87
+ <div class="rail-header" [class.position-end]="railPosition() === 'end'" matRipple (click)="toggleExpanded()">
88
+ @if (expanded() && (title() || subtitle()) && railPosition() === 'end') {
89
+ <div class="rail-branding position-end">
90
+ @if (title()) {
91
+ <span class="rail-title">{{ title() }}</span>
92
+ }
93
+ @if (subtitle()) {
94
+ <span class="rail-subtitle">{{ subtitle() }}</span>
95
+ }
96
+ </div>
97
+ }
98
+ <div class="rail-burger" [class.expanded]="expanded()" [class.position-end]="railPosition() === 'end'">
99
+ <!-- Menu icon (collapsed state) -->
100
+ <svg class="icon-menu" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor">
101
+ <path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z"/>
102
+ </svg>
103
+ <!-- Menu open icon (expanded state) -->
104
+ <svg class="icon-menu-open" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor">
105
+ <path d="M120-240v-80h520v80H120Zm664-40L584-480l200-200 56 56-144 144 144 144-56 56ZM120-440v-80h400v80H120Zm0-200v-80h520v80H120Z"/>
106
+ </svg>
107
+ </div>
108
+ @if (expanded() && (title() || subtitle()) && railPosition() === 'start') {
109
+ <div class="rail-branding">
110
+ @if (title()) {
111
+ <span class="rail-title">{{ title() }}</span>
112
+ }
113
+ @if (subtitle()) {
114
+ <span class="rail-subtitle">{{ subtitle() }}</span>
115
+ }
116
+ </div>
117
+ }
118
+ </div>
119
+ }
120
+ <ng-content />
121
+ `, host: {
122
+ 'class': 'mat-drawer mat-sidenav',
123
+ '[class.expanded]': 'expanded()',
124
+ '[class.position-end]': 'railPosition() === "end"'
125
+ }, styles: [":host{display:block;position:absolute;top:0;bottom:0;left:0;z-index:100;width:var(--rail-nav-collapsed-width, 72px);border:none!important;border-right:none!important;border-left:none!important;outline:none!important;box-shadow:none;background:var(--rail-nav-surface-color, var(--mat-sys-surface));transition:width .2s ease;overflow:visible}:host(.expanded){width:var(--rail-nav-expanded-width, fit-content);box-shadow:4px 0 8px #0003}:host(.position-end){left:auto;right:0}:host(.position-end.expanded){box-shadow:-4px 0 8px #0003}.rail-header{display:flex;align-items:center;gap:12px;height:var(--rail-nav-header-height, 56px);padding:16px 16px 16px 24px;box-sizing:border-box;cursor:pointer;color:var(--rail-nav-on-surface, var(--mat-sys-on-surface))}.rail-header.position-end{padding:16px 24px 16px 16px}.rail-header.position-end .rail-burger{margin-left:auto}.rail-header:hover{background:var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high))}.rail-burger{position:relative;width:24px;height:24px;flex-shrink:0}.rail-burger.position-end{transform:scaleX(-1)}.rail-burger svg{position:absolute;top:0;left:0;transition:opacity .3s ease,transform .3s ease}.rail-burger .icon-menu{opacity:1;transform:rotate(0)}.rail-burger .icon-menu-open{opacity:0;transform:rotate(-90deg)}.rail-burger.expanded .icon-menu{opacity:0;transform:rotate(90deg)}.rail-burger.expanded .icon-menu-open{opacity:1;transform:rotate(0)}.rail-burger.position-end .icon-menu{transform:rotate(0)}.rail-burger.position-end .icon-menu-open{transform:rotate(90deg)}.rail-burger.position-end.expanded .icon-menu{transform:rotate(-90deg)}.rail-burger.position-end.expanded .icon-menu-open{transform:rotate(0)}.rail-branding{display:flex;flex-direction:column;justify-content:center;gap:0;min-width:0;overflow:hidden;text-align:right;padding-top:5px}.rail-branding.position-end{text-align:left}.rail-title{font-size:16px;font-weight:500;line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--rail-nav-on-surface, var(--mat-sys-on-surface))}.rail-subtitle{font-size:11px;line-height:1;color:var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:.7}\n"] }]
126
+ }], ctorParameters: () => [], propDecorators: { railPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], hideDefaultHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideDefaultHeader", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], autoCollapse: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCollapse", required: false }] }] } });
127
+
128
+ class RailnavContainerComponent extends MatSidenavContainer {
129
+ constructor() {
130
+ super(...arguments);
131
+ /** Whether to show backdrop when expanded */
132
+ this.showBackdrop = input(true, ...(ngDevMode ? [{ debugName: "showBackdrop" }] : []));
133
+ }
134
+ ngAfterContentInit() {
135
+ super.ngAfterContentInit();
136
+ }
137
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavContainerComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
138
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: RailnavContainerComponent, isStandalone: true, selector: "rail-nav-container", inputs: { showBackdrop: { classPropertyName: "showBackdrop", publicName: "showBackdrop", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "mat-drawer-container mat-sidenav-container" }, providers: [
139
+ { provide: MatDrawerContainer, useExisting: RailnavContainerComponent }
140
+ ], queries: [{ propertyName: "railnav", first: true, predicate: RailnavComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: `
141
+ <ng-content />
142
+ @if (showBackdrop()) {
143
+ <div
144
+ class="railnav-backdrop"
145
+ [class.visible]="railnav?.expanded()"
146
+ [class.position-end]="railnav?.railPosition() === 'end'"
147
+ (click)="railnav?.collapse()">
148
+ </div>
149
+ }
150
+ `, isInline: true, styles: [":host{display:block;position:relative;height:100%;overflow:hidden}.railnav-backdrop{position:absolute;top:0;bottom:0;left:var(--rail-nav-collapsed-width, 72px);right:0;background:var(--rail-nav-backdrop-color, rgba(0, 0, 0, .4));z-index:99;cursor:pointer;opacity:0;pointer-events:none;transition:opacity .3s ease-in-out}.railnav-backdrop.position-end{left:0;right:var(--rail-nav-collapsed-width, 72px)}.railnav-backdrop.visible{opacity:1;pointer-events:auto}\n"] }); }
151
+ }
152
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavContainerComponent, decorators: [{
153
+ type: Component,
154
+ args: [{ selector: 'rail-nav-container', template: `
155
+ <ng-content />
156
+ @if (showBackdrop()) {
157
+ <div
158
+ class="railnav-backdrop"
159
+ [class.visible]="railnav?.expanded()"
160
+ [class.position-end]="railnav?.railPosition() === 'end'"
161
+ (click)="railnav?.collapse()">
162
+ </div>
163
+ }
164
+ `, host: {
165
+ 'class': 'mat-drawer-container mat-sidenav-container'
166
+ }, providers: [
167
+ { provide: MatDrawerContainer, useExisting: RailnavContainerComponent }
168
+ ], styles: [":host{display:block;position:relative;height:100%;overflow:hidden}.railnav-backdrop{position:absolute;top:0;bottom:0;left:var(--rail-nav-collapsed-width, 72px);right:0;background:var(--rail-nav-backdrop-color, rgba(0, 0, 0, .4));z-index:99;cursor:pointer;opacity:0;pointer-events:none;transition:opacity .3s ease-in-out}.railnav-backdrop.position-end{left:0;right:var(--rail-nav-collapsed-width, 72px)}.railnav-backdrop.visible{opacity:1;pointer-events:auto}\n"] }]
169
+ }], propDecorators: { showBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBackdrop", required: false }] }], railnav: [{
170
+ type: ContentChild,
171
+ args: [RailnavComponent]
172
+ }] } });
173
+
174
+ class RailnavContentComponent extends MatSidenavContent {
175
+ constructor() {
176
+ super(...arguments);
177
+ /** Optional: Position of the rail. If not set, uses the sibling rail-nav's position */
178
+ this.position = input(undefined, ...(ngDevMode ? [{ debugName: "position" }] : []));
179
+ /** Parent container that gives access to sibling rail-nav */
180
+ this.container = inject(RailnavContainerComponent, { optional: true });
181
+ /** Effective position - from input or from sibling rail-nav */
182
+ this.effectivePosition = computed(() => {
183
+ const inputValue = this.position();
184
+ if (inputValue !== undefined)
185
+ return inputValue;
186
+ return this.container?.railnav?.railPosition() ?? 'start';
187
+ }, ...(ngDevMode ? [{ debugName: "effectivePosition" }] : []));
188
+ }
189
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavContentComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
190
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.0", type: RailnavContentComponent, isStandalone: true, selector: "rail-nav-content", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.position-end": "effectivePosition() === \"end\"" }, classAttribute: "mat-drawer-content mat-sidenav-content" }, usesInheritance: true, ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:block;height:100%;overflow:auto;margin-left:var(--rail-nav-collapsed-width, 72px)}:host(.position-end){margin-left:0;margin-right:var(--rail-nav-collapsed-width, 72px)}\n"] }); }
191
+ }
192
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavContentComponent, decorators: [{
193
+ type: Component,
194
+ args: [{ selector: 'rail-nav-content', template: `<ng-content />`, host: {
195
+ 'class': 'mat-drawer-content mat-sidenav-content',
196
+ '[class.position-end]': 'effectivePosition() === "end"'
197
+ }, styles: [":host{display:block;height:100%;overflow:auto;margin-left:var(--rail-nav-collapsed-width, 72px)}:host(.position-end){margin-left:0;margin-right:var(--rail-nav-collapsed-width, 72px)}\n"] }]
198
+ }], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }] } });
199
+
200
+ class RailnavItemComponent {
201
+ constructor() {
202
+ /** Router link for navigation */
203
+ this.routerLink = input(...(ngDevMode ? [undefined, { debugName: "routerLink" }] : []));
204
+ /** Label text displayed */
205
+ this.label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : []));
206
+ /** Badge value (number, text, or true for dot badge) */
207
+ this.badge = input(...(ngDevMode ? [undefined, { debugName: "badge" }] : []));
208
+ /** Whether this item is active (for non-router usage) */
209
+ this.active = input(false, ...(ngDevMode ? [{ debugName: "active" }] : []));
210
+ /** Whether to show a badge */
211
+ this.hasBadge = computed(() => {
212
+ const b = this.badge();
213
+ return b !== undefined && b !== null && b !== false;
214
+ }, ...(ngDevMode ? [{ debugName: "hasBadge" }] : []));
215
+ /** Whether to show a small dot badge (no text) */
216
+ this.isDotBadge = computed(() => {
217
+ const b = this.badge();
218
+ return b === true || b === '';
219
+ }, ...(ngDevMode ? [{ debugName: "isDotBadge" }] : []));
220
+ /** Click event (for non-router usage) */
221
+ this.itemClick = output();
222
+ /** Reference to parent rail-nav */
223
+ this.railnav = inject(RailnavComponent);
224
+ /** Whether the rail is expanded */
225
+ this.expanded = computed(() => this.railnav.expanded(), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
226
+ /** Position of the rail (start or end) */
227
+ this.position = computed(() => this.railnav.railPosition(), ...(ngDevMode ? [{ debugName: "position" }] : []));
228
+ }
229
+ /** Handle item click - emit event and optionally collapse rail */
230
+ onItemClick() {
231
+ this.itemClick.emit();
232
+ if (this.railnav.autoCollapse()) {
233
+ this.railnav.collapse();
234
+ }
235
+ }
236
+ /** Handle router link click - optionally collapse rail */
237
+ onRouterLinkClick() {
238
+ if (this.railnav.autoCollapse()) {
239
+ this.railnav.collapse();
240
+ }
241
+ }
242
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
243
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: RailnavItemComponent, isStandalone: true, selector: "rail-nav-item", inputs: { routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { properties: { "class.expanded": "expanded()", "class.position-end": "position() === \"end\"" } }, ngImport: i0, template: `
244
+ @if (routerLink()) {
245
+ <a
246
+ class="rail-item"
247
+ [class.expanded]="expanded()"
248
+ [class.position-end]="position() === 'end'"
249
+ [class.active]="active()"
250
+ [routerLink]="routerLink()"
251
+ routerLinkActive="active"
252
+ (click)="onRouterLinkClick()">
253
+ <div class="rail-item-pill">
254
+ <div class="rail-item-ripple" matRipple></div>
255
+ <div class="rail-item-icon-wrapper">
256
+ <div class="rail-item-icon">
257
+ <ng-content />
258
+ </div>
259
+ @if (hasBadge()) {
260
+ <span class="rail-badge" [class.dot]="isDotBadge()">{{ isDotBadge() ? '' : badge() }}</span>
261
+ }
262
+ </div>
263
+ <span class="rail-item-label label-inline">{{ label() }}</span>
264
+ </div>
265
+ <span class="rail-item-label label-below">{{ label() }}</span>
266
+ </a>
267
+ } @else {
268
+ <button
269
+ type="button"
270
+ class="rail-item"
271
+ [class.expanded]="expanded()"
272
+ [class.position-end]="position() === 'end'"
273
+ [class.active]="active()"
274
+ (click)="onItemClick()">
275
+ <div class="rail-item-pill">
276
+ <div class="rail-item-ripple" matRipple></div>
277
+ <div class="rail-item-icon-wrapper">
278
+ <div class="rail-item-icon">
279
+ <ng-content />
280
+ </div>
281
+ @if (hasBadge()) {
282
+ <span class="rail-badge" [class.dot]="isDotBadge()">{{ isDotBadge() ? '' : badge() }}</span>
283
+ }
284
+ </div>
285
+ <span class="rail-item-label label-inline">{{ label() }}</span>
286
+ </div>
287
+ <span class="rail-item-label label-below">{{ label() }}</span>
288
+ </button>
289
+ }
290
+ `, isInline: true, styles: [":host{display:block}.rail-item{display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;text-decoration:none;color:var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));cursor:pointer;padding:0;gap:4px;background:none;border:none;font:inherit;min-height:56px;box-sizing:border-box;outline:none;width:100%}.rail-item.position-end{align-items:flex-end}.rail-item:focus-visible .rail-item-pill{border-color:var(--rail-nav-primary, var(--mat-sys-primary))}.rail-item.position-end .rail-item-pill{justify-content:flex-end;padding-left:0;padding-right:10px}.rail-item.expanded{gap:0}.rail-item-pill{position:relative;display:flex;align-items:center;justify-content:flex-start;width:48px;height:32px;border-radius:9999px;border:2px solid transparent;overflow:visible;margin-top:12px;padding-left:10px;box-sizing:border-box;transition:background .2s ease,width .2s ease,height .2s ease,margin .2s ease}.rail-item-ripple{position:absolute;inset:0;border-radius:inherit;overflow:hidden}.rail-item:hover .rail-item-pill{background:var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high))}.rail-item.active .rail-item-pill{background:var(--rail-nav-secondary-container, var(--mat-sys-secondary-container));color:var(--rail-nav-on-secondary-container, var(--mat-sys-on-secondary-container))}.rail-item.expanded .rail-item-pill{width:auto;height:48px;border-radius:9999px;padding:0 16px 0 10px;gap:12px;justify-content:flex-start;box-sizing:border-box;margin-top:0}.rail-item.expanded.position-end .rail-item-pill{flex-direction:row-reverse;justify-content:flex-start;padding:0 10px 0 16px}:host:first-child .rail-item-pill{margin-top:8px}:host:first-child .rail-item.expanded .rail-item-pill{margin-top:0}.rail-item-icon-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0;overflow:visible}.rail-item-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px}.rail-item-icon ::ng-deep>*{font-size:24px;width:24px;height:24px}.rail-badge{position:absolute;top:-6px;right:-6px;min-width:16px;height:16px;padding:0 4px;border-radius:8px;background:var(--rail-nav-error, var(--mat-sys-error));color:var(--rail-nav-on-error, var(--mat-sys-on-error));font-size:11px;font-weight:500;line-height:16px;text-align:center;box-sizing:border-box;z-index:10}.rail-badge.dot{top:-2px;right:-2px;min-width:6px;width:6px;height:6px;padding:0;border-radius:3px}.rail-item-label.label-below{font-size:12px;font-weight:500;line-height:16px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:48px;max-height:16px;opacity:1;transition:opacity .1s ease,max-height .2s ease}.rail-item.expanded .rail-item-label.label-below{opacity:0;max-height:0;pointer-events:none}.rail-item-label.label-inline{font-size:14px;font-weight:500;line-height:24px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:0;opacity:0;pointer-events:none}.rail-item.expanded .rail-item-label.label-inline{width:auto;flex:1;opacity:1;pointer-events:auto;transition:opacity .15s ease .1s}.rail-item.expanded.position-end .rail-item-label.label-inline{text-align:right}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
291
+ }
292
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavItemComponent, decorators: [{
293
+ type: Component,
294
+ args: [{ selector: 'rail-nav-item', imports: [RouterLink, RouterLinkActive, MatRippleModule], template: `
295
+ @if (routerLink()) {
296
+ <a
297
+ class="rail-item"
298
+ [class.expanded]="expanded()"
299
+ [class.position-end]="position() === 'end'"
300
+ [class.active]="active()"
301
+ [routerLink]="routerLink()"
302
+ routerLinkActive="active"
303
+ (click)="onRouterLinkClick()">
304
+ <div class="rail-item-pill">
305
+ <div class="rail-item-ripple" matRipple></div>
306
+ <div class="rail-item-icon-wrapper">
307
+ <div class="rail-item-icon">
308
+ <ng-content />
309
+ </div>
310
+ @if (hasBadge()) {
311
+ <span class="rail-badge" [class.dot]="isDotBadge()">{{ isDotBadge() ? '' : badge() }}</span>
312
+ }
313
+ </div>
314
+ <span class="rail-item-label label-inline">{{ label() }}</span>
315
+ </div>
316
+ <span class="rail-item-label label-below">{{ label() }}</span>
317
+ </a>
318
+ } @else {
319
+ <button
320
+ type="button"
321
+ class="rail-item"
322
+ [class.expanded]="expanded()"
323
+ [class.position-end]="position() === 'end'"
324
+ [class.active]="active()"
325
+ (click)="onItemClick()">
326
+ <div class="rail-item-pill">
327
+ <div class="rail-item-ripple" matRipple></div>
328
+ <div class="rail-item-icon-wrapper">
329
+ <div class="rail-item-icon">
330
+ <ng-content />
331
+ </div>
332
+ @if (hasBadge()) {
333
+ <span class="rail-badge" [class.dot]="isDotBadge()">{{ isDotBadge() ? '' : badge() }}</span>
334
+ }
335
+ </div>
336
+ <span class="rail-item-label label-inline">{{ label() }}</span>
337
+ </div>
338
+ <span class="rail-item-label label-below">{{ label() }}</span>
339
+ </button>
340
+ }
341
+ `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
342
+ '[class.expanded]': 'expanded()',
343
+ '[class.position-end]': 'position() === "end"'
344
+ }, styles: [":host{display:block}.rail-item{display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;text-decoration:none;color:var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));cursor:pointer;padding:0;gap:4px;background:none;border:none;font:inherit;min-height:56px;box-sizing:border-box;outline:none;width:100%}.rail-item.position-end{align-items:flex-end}.rail-item:focus-visible .rail-item-pill{border-color:var(--rail-nav-primary, var(--mat-sys-primary))}.rail-item.position-end .rail-item-pill{justify-content:flex-end;padding-left:0;padding-right:10px}.rail-item.expanded{gap:0}.rail-item-pill{position:relative;display:flex;align-items:center;justify-content:flex-start;width:48px;height:32px;border-radius:9999px;border:2px solid transparent;overflow:visible;margin-top:12px;padding-left:10px;box-sizing:border-box;transition:background .2s ease,width .2s ease,height .2s ease,margin .2s ease}.rail-item-ripple{position:absolute;inset:0;border-radius:inherit;overflow:hidden}.rail-item:hover .rail-item-pill{background:var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high))}.rail-item.active .rail-item-pill{background:var(--rail-nav-secondary-container, var(--mat-sys-secondary-container));color:var(--rail-nav-on-secondary-container, var(--mat-sys-on-secondary-container))}.rail-item.expanded .rail-item-pill{width:auto;height:48px;border-radius:9999px;padding:0 16px 0 10px;gap:12px;justify-content:flex-start;box-sizing:border-box;margin-top:0}.rail-item.expanded.position-end .rail-item-pill{flex-direction:row-reverse;justify-content:flex-start;padding:0 10px 0 16px}:host:first-child .rail-item-pill{margin-top:8px}:host:first-child .rail-item.expanded .rail-item-pill{margin-top:0}.rail-item-icon-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0;overflow:visible}.rail-item-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px}.rail-item-icon ::ng-deep>*{font-size:24px;width:24px;height:24px}.rail-badge{position:absolute;top:-6px;right:-6px;min-width:16px;height:16px;padding:0 4px;border-radius:8px;background:var(--rail-nav-error, var(--mat-sys-error));color:var(--rail-nav-on-error, var(--mat-sys-on-error));font-size:11px;font-weight:500;line-height:16px;text-align:center;box-sizing:border-box;z-index:10}.rail-badge.dot{top:-2px;right:-2px;min-width:6px;width:6px;height:6px;padding:0;border-radius:3px}.rail-item-label.label-below{font-size:12px;font-weight:500;line-height:16px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:48px;max-height:16px;opacity:1;transition:opacity .1s ease,max-height .2s ease}.rail-item.expanded .rail-item-label.label-below{opacity:0;max-height:0;pointer-events:none}.rail-item-label.label-inline{font-size:14px;font-weight:500;line-height:24px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:0;opacity:0;pointer-events:none}.rail-item.expanded .rail-item-label.label-inline{width:auto;flex:1;opacity:1;pointer-events:auto;transition:opacity .15s ease .1s}.rail-item.expanded.position-end .rail-item-label.label-inline{text-align:right}\n"] }]
345
+ }], propDecorators: { routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }] } });
346
+
347
+ /*
348
+ * Public API Surface of @softwarity/rail-nav
349
+ */
350
+
351
+ /**
352
+ * Generated bundle index. Do not edit.
353
+ */
354
+
355
+ export { RailnavComponent, RailnavContainerComponent, RailnavContentComponent, RailnavItemComponent };
356
+ //# sourceMappingURL=softwarity-rail-nav.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"softwarity-rail-nav.mjs","sources":["../../src/lib/railnav.component.ts","../../src/lib/railnav-container.component.ts","../../src/lib/railnav-content.component.ts","../../src/lib/railnav-item.component.ts","../../src/public-api.ts","../../src/softwarity-rail-nav.ts"],"sourcesContent":["import { Component, signal, effect, input } from '@angular/core';\nimport { MatSidenav } from '@angular/material/sidenav';\nimport { MatRippleModule } from '@angular/material/core';\n\n@Component({\n selector: 'rail-nav',\n imports: [MatRippleModule],\n template: `\n @if (!hideDefaultHeader()) {\n <div class=\"rail-header\" [class.position-end]=\"railPosition() === 'end'\" matRipple (click)=\"toggleExpanded()\">\n @if (expanded() && (title() || subtitle()) && railPosition() === 'end') {\n <div class=\"rail-branding position-end\">\n @if (title()) {\n <span class=\"rail-title\">{{ title() }}</span>\n }\n @if (subtitle()) {\n <span class=\"rail-subtitle\">{{ subtitle() }}</span>\n }\n </div>\n }\n <div class=\"rail-burger\" [class.expanded]=\"expanded()\" [class.position-end]=\"railPosition() === 'end'\">\n <!-- Menu icon (collapsed state) -->\n <svg class=\"icon-menu\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\">\n <path d=\"M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z\"/>\n </svg>\n <!-- Menu open icon (expanded state) -->\n <svg class=\"icon-menu-open\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\">\n <path d=\"M120-240v-80h520v80H120Zm664-40L584-480l200-200 56 56-144 144 144 144-56 56ZM120-440v-80h400v80H120Zm0-200v-80h520v80H120Z\"/>\n </svg>\n </div>\n @if (expanded() && (title() || subtitle()) && railPosition() === 'start') {\n <div class=\"rail-branding\">\n @if (title()) {\n <span class=\"rail-title\">{{ title() }}</span>\n }\n @if (subtitle()) {\n <span class=\"rail-subtitle\">{{ subtitle() }}</span>\n }\n </div>\n }\n </div>\n }\n <ng-content />\n `,\n styles: [`\n :host {\n display: block;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n z-index: 100;\n width: var(--rail-nav-collapsed-width, 72px);\n border: none !important;\n border-right: none !important;\n border-left: none !important;\n outline: none !important;\n box-shadow: none;\n background: var(--rail-nav-surface-color, var(--mat-sys-surface));\n transition: width 0.2s ease;\n overflow: visible;\n }\n\n :host(.expanded) {\n width: var(--rail-nav-expanded-width, fit-content);\n box-shadow: 4px 0 8px rgba(0,0,0,.2);\n }\n\n :host(.position-end) {\n left: auto;\n right: 0;\n }\n\n :host(.position-end.expanded) {\n box-shadow: -4px 0 8px rgba(0,0,0,.2);\n }\n\n .rail-header {\n display: flex;\n align-items: center;\n gap: 12px;\n height: var(--rail-nav-header-height, 56px);\n padding: 16px 16px 16px 24px;\n box-sizing: border-box;\n cursor: pointer;\n color: var(--rail-nav-on-surface, var(--mat-sys-on-surface));\n }\n\n .rail-header.position-end {\n padding: 16px 24px 16px 16px;\n }\n\n .rail-header.position-end .rail-burger {\n margin-left: auto;\n }\n\n .rail-header:hover {\n background: var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high));\n }\n\n .rail-burger {\n position: relative;\n width: 24px;\n height: 24px;\n flex-shrink: 0;\n }\n\n .rail-burger.position-end {\n transform: scaleX(-1);\n }\n\n .rail-burger svg {\n position: absolute;\n top: 0;\n left: 0;\n transition: opacity 0.3s ease, transform 0.3s ease;\n }\n\n /* Position start (left) - clockwise rotation */\n .rail-burger .icon-menu {\n opacity: 1;\n transform: rotate(0deg);\n }\n\n .rail-burger .icon-menu-open {\n opacity: 0;\n transform: rotate(-90deg);\n }\n\n .rail-burger.expanded .icon-menu {\n opacity: 0;\n transform: rotate(90deg);\n }\n\n .rail-burger.expanded .icon-menu-open {\n opacity: 1;\n transform: rotate(0deg);\n }\n\n /* Position end (right) - counter-clockwise rotation (mirrored) */\n .rail-burger.position-end .icon-menu {\n transform: rotate(0deg);\n }\n\n .rail-burger.position-end .icon-menu-open {\n transform: rotate(90deg);\n }\n\n .rail-burger.position-end.expanded .icon-menu {\n transform: rotate(-90deg);\n }\n\n .rail-burger.position-end.expanded .icon-menu-open {\n transform: rotate(0deg);\n }\n\n .rail-branding {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 0;\n min-width: 0;\n overflow: hidden;\n text-align: right;\n padding-top: 5px;\n }\n\n .rail-branding.position-end {\n text-align: left;\n }\n\n .rail-title {\n font-size: 16px;\n font-weight: 500;\n line-height: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--rail-nav-on-surface, var(--mat-sys-on-surface));\n }\n\n .rail-subtitle {\n font-size: 11px;\n line-height: 1;\n color: var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n opacity: 0.7;\n }\n `],\n host: {\n 'class': 'mat-drawer mat-sidenav',\n '[class.expanded]': 'expanded()',\n '[class.position-end]': 'railPosition() === \"end\"'\n }\n})\nexport class RailnavComponent extends MatSidenav {\n /** Position: 'start' (left) or 'end' (right) - aliased to avoid conflict with MatSidenav.position */\n readonly railPosition = input<'start' | 'end'>('start', { alias: 'position' });\n\n /** Hide the default header (burger + title/subtitle) */\n readonly hideDefaultHeader = input(false);\n\n /** Title displayed when expanded */\n readonly title = input<string>();\n\n /** Subtitle displayed when expanded */\n readonly subtitle = input<string>();\n\n /** Whether to auto-collapse when an item is clicked */\n readonly autoCollapse = input(true);\n\n /** Whether the rail is expanded to show labels */\n readonly expanded = signal(false);\n\n constructor() {\n super();\n // Default settings for rail behavior\n this.opened = true;\n this.disableClose = true;\n this.mode = 'side';\n\n // Sync mode with expanded state\n effect(() => {\n this.mode = this.expanded() ? 'over' : 'side';\n });\n }\n\n /** Toggle between collapsed (rail) and expanded (drawer) */\n toggleExpanded(): void {\n this.expanded.update(e => !e);\n }\n\n /** Collapse the rail */\n collapse(): void {\n this.expanded.set(false);\n }\n\n /** Expand the rail to drawer */\n expand(): void {\n this.expanded.set(true);\n }\n}\n","import { Component, ContentChild, AfterContentInit, input } from '@angular/core';\nimport { MatSidenavContainer, MatDrawerContainer } from '@angular/material/sidenav';\nimport { RailnavComponent } from './railnav.component';\n\n@Component({\n selector: 'rail-nav-container',\n template: `\n <ng-content />\n @if (showBackdrop()) {\n <div\n class=\"railnav-backdrop\"\n [class.visible]=\"railnav?.expanded()\"\n [class.position-end]=\"railnav?.railPosition() === 'end'\"\n (click)=\"railnav?.collapse()\">\n </div>\n }\n `,\n styles: [`\n :host {\n display: block;\n position: relative;\n height: 100%;\n overflow: hidden;\n }\n\n .railnav-backdrop {\n position: absolute;\n top: 0;\n bottom: 0;\n left: var(--rail-nav-collapsed-width, 72px);\n right: 0;\n background: var(--rail-nav-backdrop-color, rgba(0, 0, 0, 0.4));\n z-index: 99;\n cursor: pointer;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.3s ease-in-out;\n }\n\n .railnav-backdrop.position-end {\n left: 0;\n right: var(--rail-nav-collapsed-width, 72px);\n }\n\n .railnav-backdrop.visible {\n opacity: 1;\n pointer-events: auto;\n }\n `],\n host: {\n 'class': 'mat-drawer-container mat-sidenav-container'\n },\n providers: [\n { provide: MatDrawerContainer, useExisting: RailnavContainerComponent }\n ]\n})\nexport class RailnavContainerComponent extends MatSidenavContainer implements AfterContentInit {\n /** Whether to show backdrop when expanded */\n readonly showBackdrop = input(true);\n\n @ContentChild(RailnavComponent) railnav?: RailnavComponent;\n\n override ngAfterContentInit(): void {\n super.ngAfterContentInit();\n }\n}\n","import { Component, input, inject, computed } from '@angular/core';\nimport { MatSidenavContent } from '@angular/material/sidenav';\nimport { RailnavContainerComponent } from './railnav-container.component';\n\n@Component({\n selector: 'rail-nav-content',\n template: `<ng-content />`,\n styles: [`\n :host {\n display: block;\n height: 100%;\n overflow: auto;\n margin-left: var(--rail-nav-collapsed-width, 72px);\n }\n\n :host(.position-end) {\n margin-left: 0;\n margin-right: var(--rail-nav-collapsed-width, 72px);\n }\n `],\n host: {\n 'class': 'mat-drawer-content mat-sidenav-content',\n '[class.position-end]': 'effectivePosition() === \"end\"'\n }\n})\nexport class RailnavContentComponent extends MatSidenavContent {\n /** Optional: Position of the rail. If not set, uses the sibling rail-nav's position */\n readonly position = input<'start' | 'end' | undefined>(undefined);\n\n /** Parent container that gives access to sibling rail-nav */\n private container = inject(RailnavContainerComponent, { optional: true });\n\n /** Effective position - from input or from sibling rail-nav */\n protected readonly effectivePosition = computed(() => {\n const inputValue = this.position();\n if (inputValue !== undefined) return inputValue;\n return this.container?.railnav?.railPosition() ?? 'start';\n });\n}\n","import { Component, input, output, computed, inject, ChangeDetectionStrategy } from '@angular/core';\nimport { RouterLink, RouterLinkActive } from '@angular/router';\nimport { MatRippleModule } from '@angular/material/core';\nimport { RailnavComponent } from './railnav.component';\n\n@Component({\n selector: 'rail-nav-item',\n imports: [RouterLink, RouterLinkActive, MatRippleModule],\n template: `\n @if (routerLink()) {\n <a\n class=\"rail-item\"\n [class.expanded]=\"expanded()\"\n [class.position-end]=\"position() === 'end'\"\n [class.active]=\"active()\"\n [routerLink]=\"routerLink()\"\n routerLinkActive=\"active\"\n (click)=\"onRouterLinkClick()\">\n <div class=\"rail-item-pill\">\n <div class=\"rail-item-ripple\" matRipple></div>\n <div class=\"rail-item-icon-wrapper\">\n <div class=\"rail-item-icon\">\n <ng-content />\n </div>\n @if (hasBadge()) {\n <span class=\"rail-badge\" [class.dot]=\"isDotBadge()\">{{ isDotBadge() ? '' : badge() }}</span>\n }\n </div>\n <span class=\"rail-item-label label-inline\">{{ label() }}</span>\n </div>\n <span class=\"rail-item-label label-below\">{{ label() }}</span>\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"rail-item\"\n [class.expanded]=\"expanded()\"\n [class.position-end]=\"position() === 'end'\"\n [class.active]=\"active()\"\n (click)=\"onItemClick()\">\n <div class=\"rail-item-pill\">\n <div class=\"rail-item-ripple\" matRipple></div>\n <div class=\"rail-item-icon-wrapper\">\n <div class=\"rail-item-icon\">\n <ng-content />\n </div>\n @if (hasBadge()) {\n <span class=\"rail-badge\" [class.dot]=\"isDotBadge()\">{{ isDotBadge() ? '' : badge() }}</span>\n }\n </div>\n <span class=\"rail-item-label label-inline\">{{ label() }}</span>\n </div>\n <span class=\"rail-item-label label-below\">{{ label() }}</span>\n </button>\n }\n `,\n styles: [`\n :host {\n display: block;\n }\n\n .rail-item {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: flex-start;\n text-decoration: none;\n color: var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));\n cursor: pointer;\n padding: 0;\n gap: 4px;\n background: none;\n border: none;\n font: inherit;\n min-height: 56px;\n box-sizing: border-box;\n outline: none;\n width: 100%;\n }\n\n .rail-item.position-end {\n align-items: flex-end;\n }\n\n .rail-item:focus-visible .rail-item-pill {\n border-color: var(--rail-nav-primary, var(--mat-sys-primary));\n }\n\n .rail-item.position-end .rail-item-pill {\n justify-content: flex-end;\n padding-left: 0;\n padding-right: 10px;\n }\n\n .rail-item.expanded {\n gap: 0;\n }\n\n .rail-item-pill {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: flex-start;\n width: 48px;\n height: 32px;\n border-radius: 9999px;\n border: 2px solid transparent;\n overflow: visible;\n margin-top: 12px;\n padding-left: 10px;\n box-sizing: border-box;\n transition: background 0.2s ease, width 0.2s ease, height 0.2s ease, margin 0.2s ease;\n }\n\n .rail-item-ripple {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n overflow: hidden;\n }\n\n .rail-item:hover .rail-item-pill {\n background: var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high));\n }\n\n .rail-item.active .rail-item-pill {\n background: var(--rail-nav-secondary-container, var(--mat-sys-secondary-container));\n color: var(--rail-nav-on-secondary-container, var(--mat-sys-on-secondary-container));\n }\n\n /* Expanded pill includes both icon and label */\n .rail-item.expanded .rail-item-pill {\n width: auto;\n height: 48px;\n border-radius: 9999px;\n padding: 0 16px 0 10px;\n gap: 12px;\n justify-content: flex-start;\n box-sizing: border-box;\n margin-top: 0;\n }\n\n .rail-item.expanded.position-end .rail-item-pill {\n flex-direction: row-reverse;\n justify-content: flex-start;\n padding: 0 10px 0 16px;\n }\n\n /* First item: reduce space after header and keep icon stable during expand */\n /* Collapsed: 8px + 16px (half of 32px) = 24px from top */\n /* Expanded: 0px + 24px (half of 48px) = 24px from top */\n :host:first-child .rail-item-pill {\n margin-top: 8px;\n }\n\n :host:first-child .rail-item.expanded .rail-item-pill {\n margin-top: 0;\n }\n\n .rail-item-icon-wrapper {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n overflow: visible;\n }\n\n .rail-item-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n }\n\n .rail-item-icon ::ng-deep > * {\n font-size: 24px;\n width: 24px;\n height: 24px;\n }\n\n .rail-badge {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n border-radius: 8px;\n background: var(--rail-nav-error, var(--mat-sys-error));\n color: var(--rail-nav-on-error, var(--mat-sys-on-error));\n font-size: 11px;\n font-weight: 500;\n line-height: 16px;\n text-align: center;\n box-sizing: border-box;\n z-index: 10;\n }\n\n /* Small dot badge (no text) */\n .rail-badge.dot {\n top: -2px;\n right: -2px;\n min-width: 6px;\n width: 6px;\n height: 6px;\n padding: 0;\n border-radius: 3px;\n }\n\n /* Label below icon (collapsed mode) */\n .rail-item-label.label-below {\n font-size: 12px;\n font-weight: 500;\n line-height: 16px;\n text-align: center;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 48px;\n max-height: 16px;\n opacity: 1;\n transition: opacity 0.1s ease, max-height 0.2s ease;\n }\n\n .rail-item.expanded .rail-item-label.label-below {\n opacity: 0;\n max-height: 0;\n pointer-events: none;\n }\n\n /* Label inline with icon (expanded mode) */\n .rail-item-label.label-inline {\n font-size: 14px;\n font-weight: 500;\n line-height: 24px;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 0;\n opacity: 0;\n pointer-events: none;\n }\n\n .rail-item.expanded .rail-item-label.label-inline {\n width: auto;\n flex: 1;\n opacity: 1;\n pointer-events: auto;\n transition: opacity 0.15s ease 0.1s;\n }\n\n .rail-item.expanded.position-end .rail-item-label.label-inline {\n text-align: right;\n }\n `],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class.expanded]': 'expanded()',\n '[class.position-end]': 'position() === \"end\"'\n }\n})\nexport class RailnavItemComponent {\n /** Router link for navigation */\n readonly routerLink = input<string | any[]>();\n\n /** Label text displayed */\n readonly label = input<string>();\n\n /** Badge value (number, text, or true for dot badge) */\n readonly badge = input<string | number | boolean>();\n\n /** Whether this item is active (for non-router usage) */\n readonly active = input(false);\n\n /** Whether to show a badge */\n protected readonly hasBadge = computed(() => {\n const b = this.badge();\n return b !== undefined && b !== null && b !== false;\n });\n\n /** Whether to show a small dot badge (no text) */\n protected readonly isDotBadge = computed(() => {\n const b = this.badge();\n return b === true || b === '';\n });\n\n /** Click event (for non-router usage) */\n readonly itemClick = output<void>();\n\n /** Reference to parent rail-nav */\n private railnav = inject(RailnavComponent);\n\n /** Whether the rail is expanded */\n protected expanded = computed(() => this.railnav.expanded());\n\n /** Position of the rail (start or end) */\n protected position = computed(() => this.railnav.railPosition());\n\n /** Handle item click - emit event and optionally collapse rail */\n protected onItemClick(): void {\n this.itemClick.emit();\n if (this.railnav.autoCollapse()) {\n this.railnav.collapse();\n }\n }\n\n /** Handle router link click - optionally collapse rail */\n protected onRouterLinkClick(): void {\n if (this.railnav.autoCollapse()) {\n this.railnav.collapse();\n }\n }\n}\n","/*\n * Public API Surface of @softwarity/rail-nav\n */\n\nexport { RailnavComponent } from './lib/railnav.component';\nexport { RailnavContainerComponent } from './lib/railnav-container.component';\nexport { RailnavContentComponent } from './lib/railnav-content.component';\nexport { RailnavItemComponent } from './lib/railnav-item.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,45 @@
1
+ {
2
+ "name": "@softwarity/rail-nav",
3
+ "version": "1.0.1",
4
+ "author": "Softwarity",
5
+ "license": "MIT",
6
+ "description": "Angular Material Navigation Rail component - Material Design 3 compliant",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/softwarity/rail-nav.git"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "keywords": [
15
+ "angular",
16
+ "material",
17
+ "navigation",
18
+ "rail",
19
+ "sidenav",
20
+ "drawer",
21
+ "md3",
22
+ "component"
23
+ ],
24
+ "peerDependencies": {
25
+ "@angular/cdk": ">=21.0.0",
26
+ "@angular/common": ">=21.0.0",
27
+ "@angular/core": ">=21.0.0",
28
+ "@angular/material": ">=21.0.0"
29
+ },
30
+ "dependencies": {
31
+ "tslib": "^2.6.2"
32
+ },
33
+ "sideEffects": false,
34
+ "module": "fesm2022/softwarity-rail-nav.mjs",
35
+ "typings": "types/softwarity-rail-nav.d.ts",
36
+ "exports": {
37
+ "./package.json": {
38
+ "default": "./package.json"
39
+ },
40
+ ".": {
41
+ "types": "./types/softwarity-rail-nav.d.ts",
42
+ "default": "./fesm2022/softwarity-rail-nav.mjs"
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,89 @@
1
+ @use 'sass:map';
2
+
3
+ /// Available tokens for rail-nav theming
4
+ ///
5
+ /// @param {Map} $tokens - Map of tokens to override
6
+ /// - collapsed-width: Width of the rail when collapsed (default: 72px)
7
+ /// - expanded-width: Width of the rail when expanded (default: fit-content)
8
+ /// - header-height: Height of the header area (default: 56px)
9
+ /// - backdrop-color: Background color for the backdrop overlay
10
+ /// - surface-color: Background color for the rail
11
+ /// - surface-container-high: Background color for hover states
12
+ /// - on-surface: Text color for primary content
13
+ /// - on-surface-variant: Text color for secondary content
14
+ /// - secondary-container: Background color for active items
15
+ /// - on-secondary-container: Text color for active items
16
+ /// - primary: Focus ring color
17
+ /// - error: Badge background color
18
+ /// - on-error: Badge text color
19
+ ///
20
+ /// @example
21
+ /// @use '@softwarity/rail-nav' as rail-nav;
22
+ ///
23
+ /// :root {
24
+ /// @include rail-nav.overrides((
25
+ /// collapsed-width: 80px,
26
+ /// expanded-width: 280px,
27
+ /// header-height: 64px,
28
+ /// backdrop-color: rgba(0, 0, 0, 0.5),
29
+ /// primary: #6200ea,
30
+ /// ));
31
+ /// }
32
+ @mixin overrides($tokens: ()) {
33
+ $defaults: (
34
+ collapsed-width: null,
35
+ expanded-width: null,
36
+ header-height: null,
37
+ backdrop-color: null,
38
+ surface-color: null,
39
+ surface-container-high: null,
40
+ on-surface: null,
41
+ on-surface-variant: null,
42
+ secondary-container: null,
43
+ on-secondary-container: null,
44
+ primary: null,
45
+ error: null,
46
+ on-error: null
47
+ );
48
+ $merged: map.merge($defaults, $tokens);
49
+
50
+ @if map.get($merged, collapsed-width) {
51
+ --rail-nav-collapsed-width: #{map.get($merged, collapsed-width)};
52
+ }
53
+ @if map.get($merged, expanded-width) {
54
+ --rail-nav-expanded-width: #{map.get($merged, expanded-width)};
55
+ }
56
+ @if map.get($merged, header-height) {
57
+ --rail-nav-header-height: #{map.get($merged, header-height)};
58
+ }
59
+ @if map.get($merged, backdrop-color) {
60
+ --rail-nav-backdrop-color: #{map.get($merged, backdrop-color)};
61
+ }
62
+ @if map.get($merged, surface-color) {
63
+ --rail-nav-surface-color: #{map.get($merged, surface-color)};
64
+ }
65
+ @if map.get($merged, surface-container-high) {
66
+ --rail-nav-surface-container-high: #{map.get($merged, surface-container-high)};
67
+ }
68
+ @if map.get($merged, on-surface) {
69
+ --rail-nav-on-surface: #{map.get($merged, on-surface)};
70
+ }
71
+ @if map.get($merged, on-surface-variant) {
72
+ --rail-nav-on-surface-variant: #{map.get($merged, on-surface-variant)};
73
+ }
74
+ @if map.get($merged, secondary-container) {
75
+ --rail-nav-secondary-container: #{map.get($merged, secondary-container)};
76
+ }
77
+ @if map.get($merged, on-secondary-container) {
78
+ --rail-nav-on-secondary-container: #{map.get($merged, on-secondary-container)};
79
+ }
80
+ @if map.get($merged, primary) {
81
+ --rail-nav-primary: #{map.get($merged, primary)};
82
+ }
83
+ @if map.get($merged, error) {
84
+ --rail-nav-error: #{map.get($merged, error)};
85
+ }
86
+ @if map.get($merged, on-error) {
87
+ --rail-nav-on-error: #{map.get($merged, on-error)};
88
+ }
89
+ }
@@ -0,0 +1,78 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { AfterContentInit } from '@angular/core';
3
+ import { MatSidenav, MatSidenavContainer, MatSidenavContent } from '@angular/material/sidenav';
4
+
5
+ declare class RailnavComponent extends MatSidenav {
6
+ /** Position: 'start' (left) or 'end' (right) - aliased to avoid conflict with MatSidenav.position */
7
+ readonly railPosition: _angular_core.InputSignal<"start" | "end">;
8
+ /** Hide the default header (burger + title/subtitle) */
9
+ readonly hideDefaultHeader: _angular_core.InputSignal<boolean>;
10
+ /** Title displayed when expanded */
11
+ readonly title: _angular_core.InputSignal<string | undefined>;
12
+ /** Subtitle displayed when expanded */
13
+ readonly subtitle: _angular_core.InputSignal<string | undefined>;
14
+ /** Whether to auto-collapse when an item is clicked */
15
+ readonly autoCollapse: _angular_core.InputSignal<boolean>;
16
+ /** Whether the rail is expanded to show labels */
17
+ readonly expanded: _angular_core.WritableSignal<boolean>;
18
+ constructor();
19
+ /** Toggle between collapsed (rail) and expanded (drawer) */
20
+ toggleExpanded(): void;
21
+ /** Collapse the rail */
22
+ collapse(): void;
23
+ /** Expand the rail to drawer */
24
+ expand(): void;
25
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<RailnavComponent, never>;
26
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<RailnavComponent, "rail-nav", never, { "railPosition": { "alias": "position"; "required": false; "isSignal": true; }; "hideDefaultHeader": { "alias": "hideDefaultHeader"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "autoCollapse": { "alias": "autoCollapse"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
27
+ }
28
+
29
+ declare class RailnavContainerComponent extends MatSidenavContainer implements AfterContentInit {
30
+ /** Whether to show backdrop when expanded */
31
+ readonly showBackdrop: _angular_core.InputSignal<boolean>;
32
+ railnav?: RailnavComponent;
33
+ ngAfterContentInit(): void;
34
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<RailnavContainerComponent, never>;
35
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<RailnavContainerComponent, "rail-nav-container", never, { "showBackdrop": { "alias": "showBackdrop"; "required": false; "isSignal": true; }; }, {}, ["railnav"], ["*"], true, never>;
36
+ }
37
+
38
+ declare class RailnavContentComponent extends MatSidenavContent {
39
+ /** Optional: Position of the rail. If not set, uses the sibling rail-nav's position */
40
+ readonly position: _angular_core.InputSignal<"start" | "end" | undefined>;
41
+ /** Parent container that gives access to sibling rail-nav */
42
+ private container;
43
+ /** Effective position - from input or from sibling rail-nav */
44
+ protected readonly effectivePosition: _angular_core.Signal<"start" | "end">;
45
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<RailnavContentComponent, never>;
46
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<RailnavContentComponent, "rail-nav-content", never, { "position": { "alias": "position"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
47
+ }
48
+
49
+ declare class RailnavItemComponent {
50
+ /** Router link for navigation */
51
+ readonly routerLink: _angular_core.InputSignal<string | any[] | undefined>;
52
+ /** Label text displayed */
53
+ readonly label: _angular_core.InputSignal<string | undefined>;
54
+ /** Badge value (number, text, or true for dot badge) */
55
+ readonly badge: _angular_core.InputSignal<string | number | boolean | undefined>;
56
+ /** Whether this item is active (for non-router usage) */
57
+ readonly active: _angular_core.InputSignal<boolean>;
58
+ /** Whether to show a badge */
59
+ protected readonly hasBadge: _angular_core.Signal<boolean>;
60
+ /** Whether to show a small dot badge (no text) */
61
+ protected readonly isDotBadge: _angular_core.Signal<boolean>;
62
+ /** Click event (for non-router usage) */
63
+ readonly itemClick: _angular_core.OutputEmitterRef<void>;
64
+ /** Reference to parent rail-nav */
65
+ private railnav;
66
+ /** Whether the rail is expanded */
67
+ protected expanded: _angular_core.Signal<boolean>;
68
+ /** Position of the rail (start or end) */
69
+ protected position: _angular_core.Signal<"start" | "end">;
70
+ /** Handle item click - emit event and optionally collapse rail */
71
+ protected onItemClick(): void;
72
+ /** Handle router link click - optionally collapse rail */
73
+ protected onRouterLinkClick(): void;
74
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<RailnavItemComponent, never>;
75
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<RailnavItemComponent, "rail-nav-item", never, { "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; }, { "itemClick": "itemClick"; }, never, ["*", "*"], true, never>;
76
+ }
77
+
78
+ export { RailnavComponent, RailnavContainerComponent, RailnavContentComponent, RailnavItemComponent };