@flusys/ng-layout 4.0.2 → 4.1.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/README.md +367 -594
- package/assets/layout/_footer.scss +11 -1
- package/assets/layout/_topbar.scss +24 -0
- package/assets/layout/_topbar_nav.scss +1 -6
- package/fesm2022/flusys-ng-layout.mjs +23 -21
- package/fesm2022/flusys-ng-layout.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,745 +1,518 @@
|
|
|
1
|
-
# @flusys/ng-layout
|
|
1
|
+
# @flusys/ng-layout
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`@flusys/ng-layout` provides the application shell, menu system, theme management, and layout components for FLUSYS applications.
|
|
6
|
-
|
|
7
|
-
**Key Principle:** ng-layout depends on ng-core and ng-shared, but remains independent of feature packages like ng-auth via injection tokens.
|
|
3
|
+
> Application shell and layout system for the FLUSYS Angular platform — topbar, sidebar, menu, configurator, and layout state management.
|
|
8
4
|
|
|
9
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@flusys/ng-layout)
|
|
6
|
+
[](https://angular.io)
|
|
7
|
+
[](https://www.typescriptlang.org)
|
|
8
|
+
[](https://primeng.org)
|
|
9
|
+
[](LICENSE)
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|----------|-------|
|
|
13
|
-
| Package | `@flusys/ng-layout` |
|
|
14
|
-
| Version | 4.0.2 |
|
|
15
|
-
| Dependencies | ng-core, ng-shared |
|
|
16
|
-
| Build | `npm run build:ng-layout` |
|
|
17
|
-
|
|
18
|
-
## Architecture
|
|
11
|
+
---
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
└── apps-filter.util.ts
|
|
47
|
-
```
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Overview](#overview)
|
|
16
|
+
- [Features](#features)
|
|
17
|
+
- [Compatibility](#compatibility)
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Quick Start](#quick-start)
|
|
20
|
+
- [Layout Configuration](#layout-configuration)
|
|
21
|
+
- [LayoutConfig](#layoutconfig)
|
|
22
|
+
- [Menu Modes](#menu-modes)
|
|
23
|
+
- [Themes & Colors](#themes--colors)
|
|
24
|
+
- [Injection Tokens](#injection-tokens)
|
|
25
|
+
- [LayoutService](#layoutservice)
|
|
26
|
+
- [LayoutState](#layoutstate)
|
|
27
|
+
- [Components](#components)
|
|
28
|
+
- [AppLayoutComponent](#applayoutcomponent)
|
|
29
|
+
- [AppTopbarComponent](#apptopbarcomponent)
|
|
30
|
+
- [AppSidebarComponent](#appsidebarcomponent)
|
|
31
|
+
- [AppMenuComponent](#appmenucomponent)
|
|
32
|
+
- [AppMenuitemComponent](#appmenuitemcomponent)
|
|
33
|
+
- [AppCompanyBranchSelectorComponent](#appcompanybranchselectorcomponent)
|
|
34
|
+
- [AppConfiguratorComponent](#appconfiguratorscomponent)
|
|
35
|
+
- [Configuration Persistence](#configuration-persistence)
|
|
36
|
+
- [Integration Tokens](#integration-tokens)
|
|
37
|
+
- [Troubleshooting](#troubleshooting)
|
|
38
|
+
- [License](#license)
|
|
48
39
|
|
|
49
40
|
---
|
|
50
41
|
|
|
51
|
-
##
|
|
42
|
+
## Overview
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
`@flusys/ng-layout` provides the complete application shell for FLUSYS apps. It includes the topbar, sidebar, menu system, and layout configurator panel — all powered by Angular 21 signals and PrimeNG.
|
|
54
45
|
|
|
55
|
-
|
|
46
|
+
The layout integrates with auth, notification, and localization packages through injection token interfaces, maintaining clean package independence via the Provider Interface Pattern.
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
import { AppLayout, LayoutService } from '@flusys/ng-layout';
|
|
59
|
-
|
|
60
|
-
@Component({
|
|
61
|
-
selector: 'app-root',
|
|
62
|
-
imports: [AppLayout],
|
|
63
|
-
template: `<app-layout><router-outlet /></app-layout>`,
|
|
64
|
-
host: { '[class.app-dark]': 'layoutService.isDarkTheme()' },
|
|
65
|
-
})
|
|
66
|
-
export class AppComponent {
|
|
67
|
-
readonly layoutService = inject(LayoutService);
|
|
68
|
-
}
|
|
69
|
-
```
|
|
48
|
+
---
|
|
70
49
|
|
|
71
|
-
|
|
50
|
+
## Features
|
|
72
51
|
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
52
|
+
- ✅ Three menu modes: `static`, `overlay`, `topbar`
|
|
53
|
+
- ✅ Signal-based layout state management (`LayoutService`)
|
|
54
|
+
- ✅ Layout persistence (theme, color, mode saved to localStorage)
|
|
55
|
+
- ✅ Company/branch selector in sidebar
|
|
56
|
+
- ✅ Topbar user profile dropdown with avatar
|
|
57
|
+
- ✅ Notification bell integration via injection token
|
|
58
|
+
- ✅ Language selector integration via injection token
|
|
59
|
+
- ✅ Responsive mobile layout
|
|
60
|
+
- ✅ Fully configurable color palettes and themes
|
|
61
|
+
- ✅ Zoneless-compatible
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
| Class | Condition |
|
|
80
|
-
|-------|-----------|
|
|
81
|
-
| `layout-static` | Static menu mode |
|
|
82
|
-
| `layout-overlay` | Overlay menu mode |
|
|
83
|
-
| `layout-static-inactive` | Static menu collapsed on desktop |
|
|
84
|
-
| `layout-overlay-active` | Overlay menu open |
|
|
85
|
-
| `layout-mobile-active` | Mobile menu open |
|
|
63
|
+
---
|
|
86
64
|
|
|
87
|
-
|
|
88
|
-
- Subscribes to `layoutService.overlayOpen$` for outside click handling
|
|
89
|
-
- Auto-hides menu on route navigation
|
|
90
|
-
- Blocks body scroll when mobile menu is active
|
|
65
|
+
## Compatibility
|
|
91
66
|
|
|
92
|
-
|
|
67
|
+
| Package | Version |
|
|
68
|
+
|---------|---------|
|
|
69
|
+
| Angular | 21+ |
|
|
70
|
+
| @flusys/ng-core | 4.x |
|
|
71
|
+
| @flusys/ng-shared | 4.x |
|
|
72
|
+
| PrimeNG | 18+ |
|
|
73
|
+
| Tailwind CSS | 3+ |
|
|
93
74
|
|
|
94
|
-
|
|
75
|
+
---
|
|
95
76
|
|
|
96
|
-
|
|
97
|
-
```
|
|
98
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
99
|
-
│ [≡] CompanyName [☀/🌙] [🎨] [...] [Apps] [🏢] [👤] │
|
|
100
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
101
|
-
```
|
|
77
|
+
## Installation
|
|
102
78
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
┌───────────────────────────────────────────────────────────────────────────────┐
|
|
106
|
-
│ CompanyName [Dashboard] [Admin] [IAM] [Storage] [☀/🌙] [🎨] [Apps] [🏢] [👤]│
|
|
107
|
-
└───────────────────────────────────────────────────────────────────────────────┘
|
|
79
|
+
```bash
|
|
80
|
+
npm install @flusys/ng-layout @flusys/ng-core @flusys/ng-shared
|
|
108
81
|
```
|
|
109
82
|
|
|
110
|
-
| Element | Description |
|
|
111
|
-
|---------|-------------|
|
|
112
|
-
| `[≡]` | Menu toggle (Static/Overlay on mobile, Topbar on desktop) |
|
|
113
|
-
| CompanyName | From `layoutService.companyName` → links to `/` |
|
|
114
|
-
| Menu Items | In topbar mode, rendered horizontally on desktop |
|
|
115
|
-
| `[☀/🌙]` | Dark mode toggle |
|
|
116
|
-
| `[🎨]` | AppConfigurator panel |
|
|
117
|
-
| `[...]` | Mobile menu (hidden on desktop) |
|
|
118
|
-
| `[Apps]` | AppLauncher (if `layoutService.hasApps()`) |
|
|
119
|
-
| `[🏢]` | AppCompanyBranchSelector (if company feature enabled) |
|
|
120
|
-
| `[👤]` | AppProfile dropdown |
|
|
121
|
-
|
|
122
|
-
**Behavior:**
|
|
123
|
-
- **Topbar on desktop:** Renders menu items horizontally with hover-triggered submenus
|
|
124
|
-
- **Topbar on mobile:** Falls back to vertical sidebar menu
|
|
125
|
-
- **Static/Overlay:** Standard sidebar behavior (unchanged)
|
|
126
|
-
|
|
127
|
-
### AppMenu & AppMenuitem
|
|
128
|
-
|
|
129
|
-
Menu container renders permission-filtered items from `layoutService.menu`.
|
|
130
|
-
|
|
131
|
-
**AppMenuitem Signal Inputs:**
|
|
132
|
-
| Input | Type | Description |
|
|
133
|
-
|-------|------|-------------|
|
|
134
|
-
| `item` | `IMenuItem` | Required menu item data |
|
|
135
|
-
| `index` | `number` | Required index in parent |
|
|
136
|
-
| `parentKey` | `string` | Optional parent key (default: `''`) |
|
|
137
|
-
|
|
138
|
-
**Router Link Active Options:**
|
|
139
|
-
- Root path (`/`) uses `'exact'` matching
|
|
140
|
-
- Other paths use `'subset'` matching for child route support
|
|
141
|
-
|
|
142
|
-
**CSS Transitions (no @angular/animations):**
|
|
143
|
-
```css
|
|
144
|
-
:host ul {
|
|
145
|
-
transition: max-height 400ms cubic-bezier(0.86, 0, 0.07, 1);
|
|
146
|
-
}
|
|
147
|
-
:host ul.submenu-collapsed { max-height: 0; }
|
|
148
|
-
:host ul.submenu-expanded { max-height: 1000px; }
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
**Topbar Mode Behavior:**
|
|
152
|
-
- `@if (layoutService.isTopbar())` - Conditional rendering for topbar-specific UI
|
|
153
|
-
- `onMouseEnter()` - Triggers submenu display on hover (topbar only)
|
|
154
|
-
- Parent link positioning tracked via `@viewChild('parentLink')` for submenu alignment
|
|
155
|
-
- Submenu visibility controlled by `submenu-expanded` / `submenu-collapsed` classes
|
|
156
|
-
- Submenus only expand/collapse in sidebar mode (`!layoutService.isTopbar()`)
|
|
157
|
-
|
|
158
83
|
---
|
|
159
84
|
|
|
160
|
-
##
|
|
85
|
+
## Quick Start
|
|
161
86
|
|
|
162
|
-
|
|
87
|
+
### 1. Set Up App Layout Route
|
|
163
88
|
|
|
164
|
-
|
|
89
|
+
```typescript
|
|
90
|
+
// app.routes.ts
|
|
91
|
+
import { Routes } from '@angular/router';
|
|
92
|
+
import { AppLayoutComponent } from '@flusys/ng-layout';
|
|
165
93
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
94
|
+
export const routes: Routes = [
|
|
95
|
+
{
|
|
96
|
+
path: '',
|
|
97
|
+
component: AppLayoutComponent,
|
|
98
|
+
children: [
|
|
99
|
+
{
|
|
100
|
+
path: 'dashboard',
|
|
101
|
+
loadComponent: () => import('./pages/dashboard/dashboard.component'),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
path: 'products',
|
|
105
|
+
loadComponent: () => import('./pages/products/product-list.component'),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
path: 'auth',
|
|
111
|
+
loadChildren: () => import('./pages/auth/auth.routes'),
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
```
|
|
171
115
|
|
|
172
|
-
###
|
|
116
|
+
### 2. Define Menu Configuration
|
|
173
117
|
|
|
174
|
-
|
|
175
|
-
-
|
|
176
|
-
|
|
177
|
-
- No sidebar visible
|
|
178
|
-
- Menu toggle only appears on mobile
|
|
118
|
+
```typescript
|
|
119
|
+
// app-menu.config.ts
|
|
120
|
+
import { IMenuItem } from '@flusys/ng-layout';
|
|
179
121
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
122
|
+
export const APP_MENU: IMenuItem[] = [
|
|
123
|
+
{
|
|
124
|
+
labelKey: 'menu.dashboard',
|
|
125
|
+
label: 'Dashboard',
|
|
126
|
+
icon: 'pi pi-home',
|
|
127
|
+
routerLink: ['/dashboard'],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
labelKey: 'menu.products',
|
|
131
|
+
label: 'Products',
|
|
132
|
+
icon: 'pi pi-box',
|
|
133
|
+
items: [
|
|
134
|
+
{
|
|
135
|
+
labelKey: 'menu.products.list',
|
|
136
|
+
label: 'All Products',
|
|
137
|
+
routerLink: ['/products'],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
labelKey: 'menu.products.create',
|
|
141
|
+
label: 'Add Product',
|
|
142
|
+
routerLink: ['/products/create'],
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
```
|
|
184
148
|
|
|
185
|
-
### Configuration
|
|
149
|
+
### 3. Provide Layout Configuration
|
|
186
150
|
|
|
187
151
|
```typescript
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
152
|
+
// app.config.ts
|
|
153
|
+
import { ApplicationConfig } from '@angular/core';
|
|
154
|
+
import { APP_CONFIG } from '@flusys/ng-core';
|
|
155
|
+
import { LAYOUT_MENU, LAYOUT_CONFIG } from '@flusys/ng-layout';
|
|
156
|
+
import { APP_MENU } from './app-menu.config';
|
|
157
|
+
import { environment } from './environments/environment';
|
|
158
|
+
|
|
159
|
+
export const appConfig: ApplicationConfig = {
|
|
160
|
+
providers: [
|
|
161
|
+
{ provide: APP_CONFIG, useValue: environment },
|
|
162
|
+
{ provide: LAYOUT_MENU, useValue: APP_MENU },
|
|
163
|
+
{
|
|
164
|
+
provide: LAYOUT_CONFIG,
|
|
165
|
+
useValue: {
|
|
166
|
+
menuMode: 'static',
|
|
167
|
+
theme: 'lara-light-blue',
|
|
168
|
+
colorScheme: 'light',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
198
173
|
```
|
|
199
174
|
|
|
200
|
-
### Styling
|
|
201
|
-
|
|
202
|
-
Topbar styles are defined in `assets/layout/_topbar_nav.scss`:
|
|
203
|
-
|
|
204
|
-
- `.layout-topbar-nav-horizontal` - Main horizontal nav container
|
|
205
|
-
- `.layout-topbar-nav-hidden` - Hide nav on mobile (Tailwind: `hidden`)
|
|
206
|
-
- Submenu positioning with `position: absolute`
|
|
207
|
-
- Chevron icon for topbar (vs angle-down for sidebar)
|
|
208
|
-
- Hover effects with opacity and visibility transitions
|
|
209
|
-
|
|
210
|
-
### AppMenuitem Changes for Topbar
|
|
211
|
-
|
|
212
|
-
When in topbar mode:
|
|
213
|
-
- Submenu toggler icon changes to `pi-chevron-right` (vs `pi-angle-down`)
|
|
214
|
-
- Parent links show `cursor: default` instead of pointer
|
|
215
|
-
- Submenus positioned absolutely with auto-adjustment logic
|
|
216
|
-
- Hover-triggered submenu display (vs click-triggered in sidebar)
|
|
217
|
-
|
|
218
175
|
---
|
|
219
176
|
|
|
220
|
-
|
|
177
|
+
## Layout Configuration
|
|
221
178
|
|
|
222
|
-
|
|
179
|
+
### LayoutConfig
|
|
223
180
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
181
|
+
```typescript
|
|
182
|
+
interface LayoutConfig {
|
|
183
|
+
/** Menu display mode */
|
|
184
|
+
menuMode: 'static' | 'overlay' | 'topbar';
|
|
227
185
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
- Profile link → `/profile`
|
|
231
|
-
- Copy SignUp Link → `{origin}/auth/register?companySlug={slug}` (hidden if sign-up disabled)
|
|
232
|
-
- Logout with toast feedback
|
|
186
|
+
/** PrimeNG theme name */
|
|
187
|
+
theme: string;
|
|
233
188
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|--------|--------|----------|
|
|
237
|
-
| `userName` | `authState.loginUserData()?.name` | `'Guest'` |
|
|
238
|
-
| `userEmail` | `authState.loginUserData()?.email` | `''` |
|
|
239
|
-
| `profilePicture` | `layoutService.userProfilePictureUrl()` | `''` |
|
|
240
|
-
| `signUpEnabled` | `APP_CONFIG` via `isSignUpEnabled()` helper | `false` |
|
|
189
|
+
/** Color scheme */
|
|
190
|
+
colorScheme: 'light' | 'dark' | 'dim';
|
|
241
191
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
- Controlled via `signUpEnabled()` computed signal
|
|
245
|
-
- Uses `isSignUpEnabled()` helper from `@flusys/ng-core`
|
|
192
|
+
/** Ripple effect */
|
|
193
|
+
ripple?: boolean;
|
|
246
194
|
|
|
247
|
-
|
|
195
|
+
/** Scale factor (12-16) */
|
|
196
|
+
scale?: number;
|
|
248
197
|
|
|
249
|
-
|
|
198
|
+
/** Menu theme */
|
|
199
|
+
menuTheme?: 'light' | 'dark' | 'colored';
|
|
250
200
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
| `branches` | Branches for selected company |
|
|
256
|
-
| `selectedCompanyId` / `selectedBranchId` | Current selection |
|
|
257
|
-
| `isLoadingCompanies` / `isLoadingBranches` | Loading states |
|
|
258
|
-
| `isSwitching` | Switch operation in progress |
|
|
259
|
-
| `canSwitch` | Computed: enabled when selection differs from current |
|
|
201
|
+
/** Card border style */
|
|
202
|
+
inputStyle?: 'outlined' | 'filled';
|
|
203
|
+
}
|
|
204
|
+
```
|
|
260
205
|
|
|
261
|
-
|
|
262
|
-
1. Click button → Panel opens, companies load
|
|
263
|
-
2. Select company → Branches load (auto-select if single branch)
|
|
264
|
-
3. Select branch → Click "Switch" → API call → JWT updated → Redirect
|
|
206
|
+
### Menu Modes
|
|
265
207
|
|
|
266
|
-
|
|
208
|
+
#### Static Mode (Default)
|
|
267
209
|
|
|
268
|
-
|
|
210
|
+
Sidebar is always visible on desktop. Collapses to icons on tablet.
|
|
269
211
|
|
|
270
212
|
```typescript
|
|
271
|
-
|
|
272
|
-
{
|
|
273
|
-
id: 'docs',
|
|
274
|
-
name: 'Docs',
|
|
275
|
-
iconType: IconTypeEnum.PrimeNg,
|
|
276
|
-
icon: 'pi pi-book',
|
|
277
|
-
url: 'https://docs.example.com',
|
|
278
|
-
permissionLogic: { id: 'docs', type: 'action', actionId: 'docs.view' }, // optional
|
|
279
|
-
},
|
|
280
|
-
];
|
|
281
|
-
layoutService.setApps(apps);
|
|
213
|
+
{ provide: LAYOUT_CONFIG, useValue: { menuMode: 'static' } }
|
|
282
214
|
```
|
|
283
215
|
|
|
284
|
-
|
|
285
|
-
- Responsive grid: `grid-cols-2 sm:grid-cols-3`
|
|
286
|
-
- External links (new tab)
|
|
287
|
-
- Permission filtering via `permissionLogic`
|
|
288
|
-
- Only visible when `layoutService.hasApps()` is true
|
|
289
|
-
|
|
290
|
-
### AppConfigurator
|
|
291
|
-
|
|
292
|
-
Theme customizer dropdown.
|
|
216
|
+
#### Overlay Mode
|
|
293
217
|
|
|
294
|
-
|
|
295
|
-
| Setting | Values |
|
|
296
|
-
|---------|--------|
|
|
297
|
-
| Primary colors | `emerald`, `green`, `lime`, `orange`, `amber`, `yellow`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`, `noir` |
|
|
298
|
-
| Surface colors | `slate`, `gray`, `zinc`, `neutral`, `stone`, `soho`, `viva`, `ocean` |
|
|
299
|
-
| Presets | `Aura` (default), `Lara`, `Nora` |
|
|
300
|
-
| Menu mode | `static`, `overlay`, `topbar` |
|
|
218
|
+
Sidebar slides over content. Click outside to close.
|
|
301
219
|
|
|
302
|
-
|
|
220
|
+
```typescript
|
|
221
|
+
{ provide: LAYOUT_CONFIG, useValue: { menuMode: 'overlay' } }
|
|
222
|
+
```
|
|
303
223
|
|
|
304
|
-
|
|
224
|
+
#### Topbar Mode (v4.0.1+)
|
|
305
225
|
|
|
306
|
-
|
|
226
|
+
Desktop renders horizontal navigation bar. Mobile falls back to vertical sidebar.
|
|
307
227
|
|
|
308
228
|
```typescript
|
|
309
|
-
|
|
310
|
-
imports: [AppFloatingConfigurator],
|
|
311
|
-
template: `<app-floating-configurator /><div class="login-form">...</div>`
|
|
312
|
-
})
|
|
313
|
-
export class LoginComponent {}
|
|
229
|
+
{ provide: LAYOUT_CONFIG, useValue: { menuMode: 'topbar' } }
|
|
314
230
|
```
|
|
315
231
|
|
|
316
|
-
|
|
232
|
+
Submenu items appear on hover in topbar mode with automatic positioning.
|
|
317
233
|
|
|
318
|
-
|
|
234
|
+
### Themes & Colors
|
|
319
235
|
|
|
320
|
-
|
|
236
|
+
Built-in themes (PrimeNG):
|
|
321
237
|
|
|
322
|
-
|
|
238
|
+
| Theme | Description |
|
|
239
|
+
|-------|-------------|
|
|
240
|
+
| `lara-light-blue` | Default — light blue |
|
|
241
|
+
| `lara-dark-blue` | Dark blue |
|
|
242
|
+
| `lara-light-indigo` | Light indigo |
|
|
243
|
+
| `lara-dark-indigo` | Dark indigo |
|
|
244
|
+
| `lara-light-purple` | Light purple |
|
|
245
|
+
| `lara-dark-purple` | Dark purple |
|
|
246
|
+
| `lara-light-teal` | Light teal |
|
|
247
|
+
| `lara-dark-teal` | Dark teal |
|
|
323
248
|
|
|
324
|
-
|
|
249
|
+
---
|
|
325
250
|
|
|
326
|
-
|
|
251
|
+
## Injection Tokens
|
|
327
252
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
253
|
+
| Token | Type | Description |
|
|
254
|
+
|-------|------|-------------|
|
|
255
|
+
| `LAYOUT_CONFIG` | `InjectionToken<LayoutConfig>` | Initial layout configuration |
|
|
256
|
+
| `LAYOUT_MENU` | `InjectionToken<IMenuItem[]>` | Application menu items |
|
|
257
|
+
| `LAYOUT_AUTH_STATE` | `InjectionToken<ILayoutAuthState>` | Auth state bridge (from ng-auth) |
|
|
258
|
+
| `LAYOUT_AUTH_API` | `InjectionToken<ILayoutAuthApi>` | Auth API bridge (from ng-auth) |
|
|
259
|
+
| `LAYOUT_NOTIFICATION_BELL` | `InjectionToken<INotificationBellProvider>` | Notification bell (from ng-notification) |
|
|
260
|
+
| `LAYOUT_LANGUAGE_SELECTOR` | `InjectionToken<ILanguageSelectorProvider>` | Language selector (from ng-localization) |
|
|
336
261
|
|
|
337
|
-
|
|
338
|
-
staticMenuDesktopInactive?: boolean;
|
|
339
|
-
overlayMenuActive?: boolean;
|
|
340
|
-
staticMenuMobileActive?: boolean;
|
|
341
|
-
menuHoverActive?: boolean;
|
|
342
|
-
topbarMenuVisible?: boolean; // For topbar mode on desktop
|
|
343
|
-
}
|
|
262
|
+
---
|
|
344
263
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
264
|
+
## LayoutService
|
|
265
|
+
|
|
266
|
+
Central signal-based service for layout state management.
|
|
348
267
|
|
|
349
|
-
#### Signals
|
|
350
|
-
|
|
351
|
-
**State Signals (readonly):**
|
|
352
|
-
| Signal | Type |
|
|
353
|
-
|--------|------|
|
|
354
|
-
| `layoutConfig` | `Signal<LayoutConfig>` |
|
|
355
|
-
| `layoutState` | `Signal<LayoutState>` |
|
|
356
|
-
| `transitionComplete` | `Signal<boolean>` |
|
|
357
|
-
| `userProfile` | `Signal<UserProfile \| null>` |
|
|
358
|
-
| `companyProfile` | `Signal<CompanyProfile \| null>` |
|
|
359
|
-
|
|
360
|
-
**Static Values (from APP_CONFIG):**
|
|
361
|
-
| Property | Fallback |
|
|
362
|
-
|----------|----------|
|
|
363
|
-
| `appName` | `DEFAULT_APP_NAME` |
|
|
364
|
-
| `authorName` | `DEFAULT_AUTHOR.name` |
|
|
365
|
-
| `authorUrl` | `DEFAULT_AUTHOR.url` |
|
|
366
|
-
|
|
367
|
-
**Computed Signals:**
|
|
368
|
-
| Signal | Description |
|
|
369
|
-
|--------|-------------|
|
|
370
|
-
| `menu` | Permission-filtered menu items |
|
|
371
|
-
| `apps` | Permission-filtered launcher apps |
|
|
372
|
-
| `hasApps` | Whether apps exist |
|
|
373
|
-
| `isDarkTheme` | Dark theme active |
|
|
374
|
-
| `isSidebarActive` | Sidebar visible |
|
|
375
|
-
| `isStatic` | Static menu mode |
|
|
376
|
-
| `isOverlay` | Overlay menu mode |
|
|
377
|
-
| `isTopbar` | Topbar menu mode (horizontal nav on desktop) |
|
|
378
|
-
| `getPrimary` / `getSurface` | Current colors |
|
|
379
|
-
| `userName` / `userEmail` | User info (with fallbacks) |
|
|
380
|
-
| `userProfilePictureUrl` / `companyLogoUrl` | Image URLs |
|
|
381
|
-
| `companyName` | Company name or appName fallback |
|
|
382
|
-
| `isAuthenticated` | User profile exists |
|
|
383
|
-
|
|
384
|
-
#### Methods
|
|
385
|
-
|
|
386
|
-
| Method | Description |
|
|
387
|
-
|--------|-------------|
|
|
388
|
-
| `onMenuToggle()` | Toggle menu (handles static/overlay/mobile) |
|
|
389
|
-
| `onMenuStateChange(event)` | Emit menu state change |
|
|
390
|
-
| `reset()` | Reset menu state |
|
|
391
|
-
| `isDesktop()` / `isMobile()` | Viewport checks (SSR-safe) |
|
|
392
|
-
| `updateLayoutConfig(config)` | Partial config update |
|
|
393
|
-
| `updateLayoutState(state)` | Partial state update |
|
|
394
|
-
| `toggleDarkMode(config?)` | Toggle `app-dark` class |
|
|
395
|
-
| `setUserProfile(profile)` | Set user profile |
|
|
396
|
-
| `setCompanyProfile(profile)` | Set company profile |
|
|
397
|
-
| `setMenu(items)` / `clearMenu()` | Menu management |
|
|
398
|
-
| `setApps(apps)` / `clearApps()` | Apps management |
|
|
399
|
-
|
|
400
|
-
#### RxJS Observables
|
|
401
|
-
|
|
402
|
-
| Observable | Purpose |
|
|
403
|
-
|------------|---------|
|
|
404
|
-
| `menuSource$` | Menu state change events |
|
|
405
|
-
| `resetSource$` | Menu reset events |
|
|
406
|
-
| `configUpdate$` | Config change events |
|
|
407
|
-
| `overlayOpen$` | Menu overlay opened |
|
|
408
|
-
|
|
409
|
-
#### Dark Mode Transition
|
|
410
|
-
|
|
411
|
-
Uses View Transitions API with fallback:
|
|
412
268
|
```typescript
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
269
|
+
import { LayoutService } from '@flusys/ng-layout';
|
|
270
|
+
|
|
271
|
+
@Component({ ... })
|
|
272
|
+
export class MyComponent {
|
|
273
|
+
private layoutService = inject(LayoutService);
|
|
274
|
+
|
|
275
|
+
// Computed signals (read-only)
|
|
276
|
+
isStatic = this.layoutService.isStatic(); // boolean
|
|
277
|
+
isOverlay = this.layoutService.isOverlay(); // boolean
|
|
278
|
+
isTopbar = this.layoutService.isTopbar(); // boolean
|
|
279
|
+
isMobile = this.layoutService.isMobile(); // boolean
|
|
280
|
+
isMenuOpen = this.layoutService.isMenuOpen(); // boolean
|
|
281
|
+
|
|
282
|
+
// Actions
|
|
283
|
+
toggleMenu(): void {
|
|
284
|
+
this.layoutService.toggleMenu();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
setTheme(theme: string): void {
|
|
288
|
+
this.layoutService.setTheme(theme);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
setMenuMode(mode: 'static' | 'overlay' | 'topbar'): void {
|
|
292
|
+
this.layoutService.setMenuMode(mode);
|
|
293
|
+
}
|
|
416
294
|
}
|
|
417
295
|
```
|
|
418
296
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
297
|
+
**LayoutService API:**
|
|
298
|
+
|
|
299
|
+
| Member | Type | Description |
|
|
300
|
+
|--------|------|-------------|
|
|
301
|
+
| `config` | `Signal<LayoutConfig>` | Current layout configuration |
|
|
302
|
+
| `state` | `Signal<LayoutState>` | Current layout state |
|
|
303
|
+
| `isStatic()` | `Signal<boolean>` | True if menu mode is static |
|
|
304
|
+
| `isOverlay()` | `Signal<boolean>` | True if menu mode is overlay |
|
|
305
|
+
| `isTopbar()` | `Signal<boolean>` | True if menu mode is topbar |
|
|
306
|
+
| `isMobile()` | `Signal<boolean>` | True on mobile viewport |
|
|
307
|
+
| `isMenuOpen()` | `Signal<boolean>` | True if sidebar is open |
|
|
308
|
+
| `toggleMenu()` | `void` | Toggle sidebar open/close |
|
|
309
|
+
| `hideMenu()` | `void` | Force sidebar closed |
|
|
310
|
+
| `setTheme(theme)` | `void` | Change PrimeNG theme |
|
|
311
|
+
| `setMenuMode(mode)` | `void` | Change menu mode |
|
|
312
|
+
| `setColorScheme(scheme)` | `void` | Change color scheme |
|
|
422
313
|
|
|
423
|
-
|
|
424
|
-
|--------|-------------|
|
|
425
|
-
| `load()` | Load and validate saved config |
|
|
426
|
-
| `save(config)` | Save config with version |
|
|
427
|
-
| `clear()` | Remove saved config |
|
|
314
|
+
---
|
|
428
315
|
|
|
429
|
-
|
|
316
|
+
## LayoutState
|
|
430
317
|
|
|
431
|
-
|
|
432
|
-
- Invalid preset → `'Aura'`
|
|
433
|
-
- Invalid menuMode → `'static'` (valid: `'static'`, `'overlay'`, `'topbar'`)
|
|
434
|
-
- Non-boolean darkTheme → `false`
|
|
435
|
-
- Version mismatch or invalid JSON → Clear and return null
|
|
318
|
+
Reactive state object managed by `LayoutService`:
|
|
436
319
|
|
|
437
|
-
|
|
320
|
+
```typescript
|
|
321
|
+
interface LayoutState {
|
|
322
|
+
/** Sidebar open on mobile */
|
|
323
|
+
staticMenuMobileActive: boolean;
|
|
438
324
|
|
|
439
|
-
|
|
325
|
+
/** Overlay sidebar open */
|
|
326
|
+
overlayMenuActive: boolean;
|
|
440
327
|
|
|
441
|
-
|
|
328
|
+
/** Desktop sidebar collapsed */
|
|
329
|
+
staticMenuDesktopInactive: boolean;
|
|
442
330
|
|
|
443
|
-
|
|
331
|
+
/** Right-click menu visible */
|
|
332
|
+
menuHoverActive: boolean;
|
|
444
333
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
interface ILayoutAuthState {
|
|
448
|
-
currentCompanyInfo: Signal<ICompanyInfo | null>;
|
|
449
|
-
currentBranchInfo: Signal<IBranchInfo | null>;
|
|
450
|
-
loginUserData: Signal<IUserInfo | null>;
|
|
451
|
-
}
|
|
334
|
+
/** Config panel open */
|
|
335
|
+
configSidebarVisible: boolean;
|
|
452
336
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
logOut(): Observable<any>;
|
|
456
|
-
navigateLogin(withUrl?: boolean): void;
|
|
457
|
-
switchCompany(companyId: string, branchId: string): Observable<any>;
|
|
458
|
-
getUserCompanies(): Observable<ICompanyInfo[]>;
|
|
459
|
-
getCompanyBranches(companyId: string): Observable<IBranchInfo[]>;
|
|
337
|
+
/** Topbar nav visible (topbar mode) */
|
|
338
|
+
topbarMenuVisible: boolean;
|
|
460
339
|
}
|
|
461
340
|
```
|
|
462
341
|
|
|
463
|
-
|
|
342
|
+
---
|
|
464
343
|
|
|
465
|
-
|
|
344
|
+
## Components
|
|
345
|
+
|
|
346
|
+
### AppLayoutComponent
|
|
347
|
+
|
|
348
|
+
Root layout component. Use this as the parent route component.
|
|
466
349
|
|
|
467
350
|
```typescript
|
|
468
|
-
|
|
469
|
-
import { LAYOUT_AUTH_STATE, LAYOUT_AUTH_API } from '@flusys/ng-layout';
|
|
470
|
-
import { AuthLayoutStateAdapter, AuthLayoutApiAdapter } from '@flusys/ng-auth';
|
|
351
|
+
import { AppLayoutComponent } from '@flusys/ng-layout';
|
|
471
352
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
{ provide: LAYOUT_AUTH_API, useExisting: AuthLayoutApiAdapter },
|
|
475
|
-
]
|
|
353
|
+
// In routes:
|
|
354
|
+
{ path: '', component: AppLayoutComponent, children: [...] }
|
|
476
355
|
```
|
|
477
356
|
|
|
478
|
-
|
|
357
|
+
Renders:
|
|
358
|
+
- `AppTopbarComponent` (header)
|
|
359
|
+
- `AppSidebarComponent` (left navigation)
|
|
360
|
+
- `<router-outlet>` (page content)
|
|
361
|
+
- `AppConfiguratorComponent` (floating settings panel)
|
|
479
362
|
|
|
480
|
-
|
|
363
|
+
### AppTopbarComponent
|
|
481
364
|
|
|
482
|
-
|
|
365
|
+
Application header bar with:
|
|
366
|
+
- Logo / app name
|
|
367
|
+
- Menu toggle button (mobile)
|
|
368
|
+
- User profile dropdown (avatar, name, logout)
|
|
369
|
+
- Sign-up link (conditionally shown based on `isSignUpEnabled()`)
|
|
370
|
+
- Notification bell slot (via `LAYOUT_NOTIFICATION_BELL` token)
|
|
371
|
+
- Language selector slot (via `LAYOUT_LANGUAGE_SELECTOR` token)
|
|
483
372
|
|
|
484
|
-
|
|
373
|
+
### AppSidebarComponent
|
|
485
374
|
|
|
486
|
-
|
|
375
|
+
Left navigation panel containing:
|
|
376
|
+
- `AppMenuComponent` (navigation items)
|
|
377
|
+
- `AppCompanyBranchSelectorComponent` (if company selection enabled)
|
|
487
378
|
|
|
488
|
-
|
|
489
|
-
2. `LayoutService.menu` / `apps` are computed signals that filter via utilities
|
|
490
|
-
3. Uses `evaluateLogicNode()` from ng-shared
|
|
379
|
+
### AppMenuComponent
|
|
491
380
|
|
|
492
|
-
|
|
381
|
+
Renders the menu tree from `LAYOUT_MENU` token. Supports:
|
|
382
|
+
- Nested submenus (unlimited depth)
|
|
383
|
+
- Active route highlighting
|
|
384
|
+
- Translation keys via `labelKey`
|
|
385
|
+
- Icons via PrimeIcons
|
|
493
386
|
|
|
494
|
-
|
|
495
|
-
interface IMenuItem {
|
|
496
|
-
id?: string; label?: string; icon?: string; iconType?: number;
|
|
497
|
-
routerLink?: string[]; children?: IMenuItem[]; separator?: boolean;
|
|
498
|
-
permissionLogic?: ILogicNode | null;
|
|
499
|
-
}
|
|
387
|
+
### AppMenuitemComponent
|
|
500
388
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
roleId?: string; // For 'role'
|
|
507
|
-
children?: ILogicNode[]; // For 'group'
|
|
508
|
-
}
|
|
509
|
-
```
|
|
389
|
+
Individual menu item component. Handles:
|
|
390
|
+
- Route navigation
|
|
391
|
+
- Submenu expand/collapse
|
|
392
|
+
- Topbar hover positioning
|
|
393
|
+
- `routerLinkActiveOptions`
|
|
510
394
|
|
|
511
|
-
###
|
|
395
|
+
### AppCompanyBranchSelectorComponent
|
|
512
396
|
|
|
513
|
-
|
|
514
|
-
// Single action check
|
|
515
|
-
permissionLogic: { id: '1', type: 'action', actionId: 'admin.access' }
|
|
516
|
-
|
|
517
|
-
// OR - user needs ANY permission
|
|
518
|
-
permissionLogic: {
|
|
519
|
-
id: 'check', type: 'group', operator: 'OR',
|
|
520
|
-
children: [
|
|
521
|
-
{ id: '1', type: 'action', actionId: 'reports.view' },
|
|
522
|
-
{ id: '2', type: 'role', roleId: 'manager-role-id' },
|
|
523
|
-
],
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// AND - user needs ALL permissions
|
|
527
|
-
permissionLogic: {
|
|
528
|
-
id: 'check', type: 'group', operator: 'AND',
|
|
529
|
-
children: [
|
|
530
|
-
{ id: '1', type: 'action', actionId: 'admin.access' },
|
|
531
|
-
{ id: '2', type: 'role', roleId: 'superuser-role-id' },
|
|
532
|
-
],
|
|
533
|
-
}
|
|
534
|
-
```
|
|
397
|
+
Dropdown selectors for company and branch. Shown in sidebar when `services.auth.features.companySelection` is enabled.
|
|
535
398
|
|
|
536
|
-
###
|
|
399
|
+
### AppConfiguratorComponent
|
|
537
400
|
|
|
538
|
-
-
|
|
539
|
-
-
|
|
540
|
-
-
|
|
401
|
+
Floating right-side panel for live layout customization:
|
|
402
|
+
- Theme picker
|
|
403
|
+
- Color scheme toggle (light/dark/dim)
|
|
404
|
+
- Menu mode selector (static/overlay/topbar)
|
|
405
|
+
- Scale slider
|
|
541
406
|
|
|
542
407
|
---
|
|
543
408
|
|
|
544
|
-
##
|
|
409
|
+
## Configuration Persistence
|
|
545
410
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
| Theme | Primary Color | Hover Color |
|
|
549
|
-
|-------|---------------|-------------|
|
|
550
|
-
| `GreenTheme` | `#01712c` | `#119744` |
|
|
551
|
-
| `NavyBlueTheme` | `#3535cd` | `#0707a9` |
|
|
411
|
+
`LayoutPersistenceService` automatically saves and restores layout preferences to `localStorage`:
|
|
552
412
|
|
|
553
413
|
```typescript
|
|
554
|
-
|
|
414
|
+
// Automatically persisted keys:
|
|
415
|
+
// - flusys.layout.theme
|
|
416
|
+
// - flusys.layout.colorScheme
|
|
417
|
+
// - flusys.layout.menuMode
|
|
418
|
+
// - flusys.layout.scale
|
|
419
|
+
// - flusys.layout.ripple
|
|
555
420
|
```
|
|
556
421
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
## Usage Examples
|
|
560
|
-
|
|
561
|
-
### Basic Setup
|
|
422
|
+
No manual setup required — persistence is active whenever `LayoutService` is injected. To clear persisted settings:
|
|
562
423
|
|
|
563
424
|
```typescript
|
|
564
|
-
@
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
host: { '[class.app-dark]': 'layoutService.isDarkTheme()' },
|
|
568
|
-
})
|
|
569
|
-
export class AppComponent {
|
|
570
|
-
readonly layoutService = inject(LayoutService);
|
|
571
|
-
}
|
|
425
|
+
import { LayoutPersistenceService } from '@flusys/ng-layout';
|
|
426
|
+
|
|
427
|
+
this.persistenceService.clear();
|
|
572
428
|
```
|
|
573
429
|
|
|
574
|
-
|
|
430
|
+
---
|
|
575
431
|
|
|
576
|
-
|
|
432
|
+
## Integration Tokens
|
|
433
|
+
|
|
434
|
+
### Auth Integration (from ng-auth)
|
|
577
435
|
|
|
578
436
|
```typescript
|
|
579
|
-
|
|
580
|
-
import { IMenuItem } from '@flusys/ng-layout';
|
|
437
|
+
import { provideAuthLayoutIntegration } from '@flusys/ng-auth';
|
|
581
438
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
labelKey: 'menu.dashboard',
|
|
586
|
-
icon: 'pi pi-home',
|
|
587
|
-
iconType: 1,
|
|
588
|
-
routerLink: ['/'],
|
|
589
|
-
},
|
|
590
|
-
{
|
|
591
|
-
id: 'admin',
|
|
592
|
-
labelKey: 'menu.administration',
|
|
593
|
-
icon: 'pi pi-cog',
|
|
594
|
-
iconType: 1,
|
|
595
|
-
permissionLogic: { id: 'admin', type: 'action', actionId: 'admin.access' },
|
|
596
|
-
children: [
|
|
597
|
-
{
|
|
598
|
-
id: 'users',
|
|
599
|
-
labelKey: 'menu.users',
|
|
600
|
-
icon: 'pi pi-users',
|
|
601
|
-
iconType: 1,
|
|
602
|
-
routerLink: ['/admin/users'],
|
|
603
|
-
},
|
|
604
|
-
],
|
|
605
|
-
},
|
|
606
|
-
];
|
|
439
|
+
// app.config.ts providers:
|
|
440
|
+
...provideAuthLayoutIntegration()
|
|
441
|
+
// Provides: LAYOUT_AUTH_STATE, LAYOUT_AUTH_API
|
|
607
442
|
```
|
|
608
443
|
|
|
609
|
-
|
|
610
|
-
- Centralized menu definition in config file
|
|
611
|
-
- Uses `labelKey` for localization (not hardcoded labels)
|
|
612
|
-
- Easier to maintain and share across multiple layouts
|
|
613
|
-
- Supports nested children and permission logic
|
|
614
|
-
|
|
615
|
-
**Usage in App:**
|
|
444
|
+
**`LAYOUT_AUTH_STATE`** interface:
|
|
616
445
|
```typescript
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
446
|
+
interface ILayoutAuthState {
|
|
447
|
+
user: Signal<ICurrentUser | null>;
|
|
448
|
+
company: Signal<ICompany | null>;
|
|
449
|
+
branch: Signal<IBranch | null>;
|
|
450
|
+
isAuthenticated: Signal<boolean>;
|
|
451
|
+
profilePictureUrl: Signal<string | null>;
|
|
452
|
+
companyLogoUrl: Signal<string | null>;
|
|
453
|
+
}
|
|
621
454
|
```
|
|
622
455
|
|
|
623
|
-
|
|
624
|
-
|
|
456
|
+
**`LAYOUT_AUTH_API`** interface:
|
|
625
457
|
```typescript
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
email: 'john@example.com',
|
|
631
|
-
profilePictureUrl: 'https://example.com/avatar.jpg',
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
layoutService.setCompanyProfile({
|
|
635
|
-
id: 'company-456',
|
|
636
|
-
name: 'Acme Corp',
|
|
637
|
-
slug: 'acme-corp',
|
|
638
|
-
logoUrl: 'https://example.com/logo.png',
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
// On logout
|
|
642
|
-
layoutService.setUserProfile(null);
|
|
643
|
-
layoutService.setCompanyProfile(null);
|
|
458
|
+
interface ILayoutAuthApi {
|
|
459
|
+
logout(): Observable<void>;
|
|
460
|
+
selectCompany(companyId: string, branchId: string): Observable<void>;
|
|
461
|
+
}
|
|
644
462
|
```
|
|
645
463
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
## Best Practices
|
|
649
|
-
|
|
650
|
-
### Signal Patterns
|
|
464
|
+
### Notification Bell Integration (from ng-notification)
|
|
651
465
|
|
|
652
|
-
Use private writable + public readonly pattern:
|
|
653
466
|
```typescript
|
|
654
|
-
|
|
655
|
-
readonly isActive = this._isActive.asReadonly();
|
|
467
|
+
import { provideNotificationProviders } from '@flusys/ng-notification';
|
|
656
468
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
this._isActive.update(v => !v);
|
|
469
|
+
...provideNotificationProviders()
|
|
470
|
+
// Provides: LAYOUT_NOTIFICATION_BELL
|
|
660
471
|
```
|
|
661
472
|
|
|
662
|
-
###
|
|
473
|
+
### Language Selector Integration (from ng-localization)
|
|
663
474
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
| AppConfigurator | `w-[calc(100vw-2rem)] sm:w-72 max-w-72` |
|
|
667
|
-
| AppProfile | `w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]` |
|
|
668
|
-
| AppCompanyBranchSelector | `w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[300px] max-w-[360px]` |
|
|
669
|
-
| AppLauncher | `w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]` |
|
|
670
|
-
|
|
671
|
-
### Dark Mode CSS Variables
|
|
475
|
+
```typescript
|
|
476
|
+
import { provideLocalization } from '@flusys/ng-localization';
|
|
672
477
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
bg-emphasis /* Hover states */
|
|
676
|
-
text-color / text-muted-color /* Text */
|
|
677
|
-
border-surface /* Borders */
|
|
678
|
-
bg-primary / text-primary-contrast /* Primary */
|
|
478
|
+
...provideLocalization()
|
|
479
|
+
// Provides: LAYOUT_LANGUAGE_SELECTOR
|
|
679
480
|
```
|
|
680
481
|
|
|
681
482
|
---
|
|
682
483
|
|
|
683
|
-
##
|
|
484
|
+
## Troubleshooting
|
|
684
485
|
|
|
685
|
-
|
|
686
|
-
|-------|----------|
|
|
687
|
-
| Menu not showing | Call `layoutService.setMenu([...])` |
|
|
688
|
-
| Auth info missing | Provide `LAYOUT_AUTH_STATE` token |
|
|
689
|
-
| Theme not applying | Add `host: { '[class.app-dark]': 'layoutService.isDarkTheme()' }` |
|
|
690
|
-
| Menu items hidden | Check `permissionLogic` and user permissions |
|
|
691
|
-
| Profile picture missing | Include `profilePictureUrl` in `setUserProfile()` |
|
|
692
|
-
| Company feature missing | Enable `services.auth.enabled: true` and provide both auth tokens |
|
|
486
|
+
**Sidebar doesn't open on mobile**
|
|
693
487
|
|
|
694
|
-
|
|
488
|
+
Ensure `AppLayoutComponent` is the parent route component, not just imported as a standalone component in a non-route context.
|
|
695
489
|
|
|
696
|
-
|
|
490
|
+
**Menu items don't highlight active route**
|
|
697
491
|
|
|
698
|
-
|
|
492
|
+
Add `routerLinkActiveOptions: { exact: true }` to leaf menu items and `{ exact: false }` to parent items:
|
|
699
493
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
| `AppFooter` | `app-footer` |
|
|
708
|
-
| `AppProfile` | `app-profile` |
|
|
709
|
-
| `AppCompanyBranchSelector` | `app-company-branch-selector` |
|
|
710
|
-
| `AppLauncher` | `app-launcher` |
|
|
711
|
-
| `AppConfigurator` | `app-configurator` |
|
|
712
|
-
| `AppFloatingConfigurator` | `app-floating-configurator` |
|
|
494
|
+
```typescript
|
|
495
|
+
{
|
|
496
|
+
labelKey: 'menu.dashboard',
|
|
497
|
+
routerLink: ['/dashboard'],
|
|
498
|
+
routerLinkActiveOptions: { exact: true },
|
|
499
|
+
}
|
|
500
|
+
```
|
|
713
501
|
|
|
714
|
-
|
|
502
|
+
**Topbar submenus appear behind other elements**
|
|
715
503
|
|
|
716
|
-
|
|
717
|
-
|---------|-------------|
|
|
718
|
-
| `LayoutService` | Signal-based layout state management |
|
|
719
|
-
| `LayoutPersistenceService` | localStorage persistence |
|
|
504
|
+
Set a higher `z-index` on `.topbar-submenu` in your global styles, or ensure no parent has `overflow: hidden`.
|
|
720
505
|
|
|
721
|
-
|
|
506
|
+
**Theme not changing**
|
|
722
507
|
|
|
723
|
-
|
|
724
|
-
|-------|-----------|
|
|
725
|
-
| `LAYOUT_AUTH_STATE` | `ILayoutAuthState` |
|
|
726
|
-
| `LAYOUT_AUTH_API` | `ILayoutAuthApi` |
|
|
508
|
+
Check that `LayoutPersistenceService` is provided (it's automatic when `LayoutService` is used). If the theme is cached, call `persistenceService.clear()` once.
|
|
727
509
|
|
|
728
|
-
|
|
510
|
+
**`No provider for LAYOUT_AUTH_STATE`**
|
|
729
511
|
|
|
730
|
-
|
|
731
|
-
|----------|-------------|
|
|
732
|
-
| `filterMenuByPermissions()` | Filter menu items by permission logic |
|
|
733
|
-
| `filterAppsByPermissions()` | Filter launcher apps by permission logic |
|
|
512
|
+
You must call `provideAuthLayoutIntegration()` in `app.config.ts` after enabling `ng-auth`.
|
|
734
513
|
|
|
735
514
|
---
|
|
736
515
|
|
|
737
|
-
##
|
|
738
|
-
|
|
739
|
-
- [CORE-GUIDE.md](./CORE-GUIDE.md) - Configuration, interceptors
|
|
740
|
-
- [SHARED-GUIDE.md](./SHARED-GUIDE.md) - Shared components, provider interfaces
|
|
741
|
-
- [AUTH-GUIDE.md](./AUTH-GUIDE.md) - Authentication integration, adapters
|
|
742
|
-
|
|
743
|
-
---
|
|
516
|
+
## License
|
|
744
517
|
|
|
745
|
-
|
|
518
|
+
MIT © FLUSYS
|