@flusys/ng-layout 2.0.0-rc.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +393 -483
- package/fesm2022/flusys-ng-layout.mjs +39 -39
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -2,343 +2,362 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
`@flusys/ng-layout` provides the application shell, menu system, theme management, and layout components for FLUSYS applications.
|
|
5
|
+
`@flusys/ng-layout` provides the application shell, menu system, theme management, and layout components for FLUSYS applications.
|
|
6
6
|
|
|
7
|
-
**Key Principle:** ng-layout depends on ng-core and ng-shared, but remains independent of feature packages like ng-auth.
|
|
7
|
+
**Key Principle:** ng-layout depends on ng-core and ng-shared, but remains independent of feature packages like ng-auth via injection tokens.
|
|
8
8
|
|
|
9
9
|
## Package Information
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
11
|
+
| Property | Value |
|
|
12
|
+
|----------|-------|
|
|
13
|
+
| Package | `@flusys/ng-layout` |
|
|
14
|
+
| Version | 3.0.0 |
|
|
15
|
+
| Dependencies | ng-core, ng-shared |
|
|
16
|
+
| Build | `npm run build:ng-layout` |
|
|
15
17
|
|
|
16
|
-
##
|
|
18
|
+
## Architecture
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
```
|
|
21
|
+
ng-layout/
|
|
22
|
+
├── components/
|
|
23
|
+
│ ├── app.layout.ts # Main layout wrapper
|
|
24
|
+
│ ├── app.topbar.ts # Application header
|
|
25
|
+
│ ├── app.sidebar.ts # Sidebar wrapper
|
|
26
|
+
│ ├── app.menu.ts # Menu container
|
|
27
|
+
│ ├── app.menuitem.ts # Recursive menu item
|
|
28
|
+
│ ├── app.footer.ts # Footer component
|
|
29
|
+
│ ├── app.configurator.ts # Theme customizer
|
|
30
|
+
│ ├── app.floatingconfigurator.ts
|
|
31
|
+
│ └── top-bar/
|
|
32
|
+
│ ├── app.profile.ts
|
|
33
|
+
│ ├── app.launcher.ts
|
|
34
|
+
│ └── app.company-branch-selector.ts
|
|
35
|
+
├── interfaces/
|
|
36
|
+
│ ├── layout-auth.interface.ts # Auth integration tokens
|
|
37
|
+
│ └── view-transitions.ts
|
|
38
|
+
├── services/
|
|
39
|
+
│ ├── layout.service.ts # Layout state management
|
|
40
|
+
│ └── layout-persistence.service.ts
|
|
41
|
+
├── theme/
|
|
42
|
+
│ ├── green-theme.ts
|
|
43
|
+
│ └── navy-blue-theme.ts
|
|
44
|
+
└── utils/
|
|
45
|
+
├── menu-filter.util.ts
|
|
46
|
+
└── apps-filter.util.ts
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Components
|
|
19
52
|
|
|
20
|
-
|
|
53
|
+
### AppLayout
|
|
21
54
|
|
|
22
55
|
Main layout wrapper with signal-based state management.
|
|
23
56
|
|
|
24
57
|
```typescript
|
|
25
|
-
import { AppLayout } from '@flusys/ng-layout';
|
|
58
|
+
import { AppLayout, LayoutService } from '@flusys/ng-layout';
|
|
26
59
|
|
|
27
60
|
@Component({
|
|
28
61
|
selector: 'app-root',
|
|
29
|
-
standalone: true,
|
|
30
62
|
imports: [AppLayout],
|
|
31
|
-
template:
|
|
32
|
-
|
|
33
|
-
<router-outlet />
|
|
34
|
-
</app-layout>
|
|
35
|
-
`
|
|
63
|
+
template: `<app-layout><router-outlet /></app-layout>`,
|
|
64
|
+
host: { '[class.app-dark]': 'layoutService.isDarkTheme()' },
|
|
36
65
|
})
|
|
37
|
-
export class AppComponent {
|
|
66
|
+
export class AppComponent {
|
|
67
|
+
readonly layoutService = inject(LayoutService);
|
|
68
|
+
}
|
|
38
69
|
```
|
|
39
70
|
|
|
40
|
-
**
|
|
41
|
-
- AppTopbar (header with logo, user menu)
|
|
42
|
-
- AppSidebar (collapsible navigation menu)
|
|
43
|
-
- Main content area with `<router-outlet>`
|
|
44
|
-
- AppFooter
|
|
71
|
+
**Includes:** AppTopbar, AppSidebar, main content area, AppFooter, layout mask overlay
|
|
45
72
|
|
|
46
|
-
**Layout
|
|
73
|
+
**Layout Modes:**
|
|
47
74
|
- **Static** - Sidebar always visible on desktop, overlay on mobile
|
|
48
75
|
- **Overlay** - Sidebar overlays content when opened
|
|
49
76
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
77
|
+
**Dynamic CSS Classes:**
|
|
78
|
+
| Class | Condition |
|
|
79
|
+
|-------|-----------|
|
|
80
|
+
| `layout-static` | Static menu mode |
|
|
81
|
+
| `layout-overlay` | Overlay menu mode |
|
|
82
|
+
| `layout-static-inactive` | Static menu collapsed on desktop |
|
|
83
|
+
| `layout-overlay-active` | Overlay menu open |
|
|
84
|
+
| `layout-mobile-active` | Mobile menu open |
|
|
53
85
|
|
|
54
|
-
**
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
- Theme/color palette configurator (AppConfigurator via pStyleClass)
|
|
59
|
-
- App launcher grid (if apps configured via `LayoutService.setApps()`)
|
|
60
|
-
- Company/Branch selector (if `enableCompanyFeature` is true)
|
|
61
|
-
- User profile dropdown (AppProfile)
|
|
62
|
-
|
|
63
|
-
#### AppSidebar
|
|
64
|
-
|
|
65
|
-
Collapsible navigation sidebar. Contains AppMenu.
|
|
66
|
-
|
|
67
|
-
**Features:**
|
|
68
|
-
- Multi-level menu via AppMenu
|
|
69
|
-
- Collapsible/expandable
|
|
70
|
-
- Active route highlighting
|
|
71
|
-
- Icons with labels
|
|
72
|
-
- Smooth CSS animations
|
|
73
|
-
|
|
74
|
-
#### AppMenu
|
|
86
|
+
**Behavior:**
|
|
87
|
+
- Subscribes to `layoutService.overlayOpen$` for outside click handling
|
|
88
|
+
- Auto-hides menu on route navigation
|
|
89
|
+
- Blocks body scroll when mobile menu is active
|
|
75
90
|
|
|
76
|
-
|
|
91
|
+
### AppTopbar
|
|
77
92
|
|
|
78
|
-
|
|
93
|
+
Application header with branding and user actions.
|
|
79
94
|
|
|
80
|
-
|
|
95
|
+
```
|
|
96
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
97
|
+
│ [≡] CompanyName [☀/🌙] [🎨] [...] [Apps] [🏢] [👤] │
|
|
98
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
99
|
+
```
|
|
81
100
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
101
|
+
| Element | Description |
|
|
102
|
+
|---------|-------------|
|
|
103
|
+
| `[≡]` | Menu toggle → `layoutService.onMenuToggle()` |
|
|
104
|
+
| CompanyName | From `layoutService.companyName` → links to `/` |
|
|
105
|
+
| `[☀/🌙]` | Dark mode toggle |
|
|
106
|
+
| `[🎨]` | AppConfigurator panel |
|
|
107
|
+
| `[...]` | Mobile menu (hidden on desktop) |
|
|
108
|
+
| `[Apps]` | AppLauncher (if `layoutService.hasApps()`) |
|
|
109
|
+
| `[🏢]` | AppCompanyBranchSelector (if company feature enabled) |
|
|
110
|
+
| `[👤]` | AppProfile dropdown |
|
|
111
|
+
|
|
112
|
+
### AppMenu & AppMenuitem
|
|
113
|
+
|
|
114
|
+
Menu container renders permission-filtered items from `layoutService.menu`.
|
|
115
|
+
|
|
116
|
+
**AppMenuitem Signal Inputs:**
|
|
117
|
+
| Input | Type | Description |
|
|
118
|
+
|-------|------|-------------|
|
|
119
|
+
| `item` | `IMenuItem` | Required menu item data |
|
|
120
|
+
| `index` | `number` | Required index in parent |
|
|
121
|
+
| `parentKey` | `string` | Optional parent key (default: `''`) |
|
|
122
|
+
|
|
123
|
+
**Router Link Active Options:**
|
|
124
|
+
- Root path (`/`) uses `'exact'` matching
|
|
125
|
+
- Other paths use `'subset'` matching for child route support
|
|
126
|
+
|
|
127
|
+
**CSS Transitions (no @angular/animations):**
|
|
128
|
+
```css
|
|
129
|
+
:host ul {
|
|
130
|
+
transition: max-height 400ms cubic-bezier(0.86, 0, 0.07, 1);
|
|
131
|
+
}
|
|
132
|
+
:host ul.submenu-collapsed { max-height: 0; }
|
|
133
|
+
:host ul.submenu-expanded { max-height: 1000px; }
|
|
134
|
+
```
|
|
87
135
|
|
|
88
|
-
|
|
136
|
+
### AppFooter
|
|
89
137
|
|
|
90
|
-
Displays
|
|
91
|
-
- **appName** - Application name from `APP_CONFIG` or default
|
|
92
|
-
- **authorName** - Author/company name with link
|
|
93
|
-
- **authorUrl** - Link to author website
|
|
138
|
+
Displays branding from `LayoutService`: `appName`, `authorName`, `authorUrl`
|
|
94
139
|
|
|
95
|
-
|
|
140
|
+
### AppProfile
|
|
96
141
|
|
|
97
142
|
User profile dropdown in topbar.
|
|
98
143
|
|
|
99
144
|
**Features:**
|
|
100
|
-
- User info
|
|
101
|
-
- Profile
|
|
102
|
-
- Copy SignUp Link
|
|
103
|
-
- Logout
|
|
104
|
-
- Responsive dropdown width (`w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]`)
|
|
105
|
-
- Uses `LAYOUT_AUTH_API` and `LAYOUT_AUTH_STATE` tokens (optional injection)
|
|
106
|
-
|
|
107
|
-
#### AppCompanyBranchSelector
|
|
145
|
+
- User info (picture, name, email) with text truncation
|
|
146
|
+
- Profile link → `/profile`
|
|
147
|
+
- Copy SignUp Link → `{origin}/auth/register?companySlug={slug}`
|
|
148
|
+
- Logout with toast feedback
|
|
108
149
|
|
|
109
|
-
|
|
150
|
+
**Computed Signals:**
|
|
151
|
+
| Signal | Source | Fallback |
|
|
152
|
+
|--------|--------|----------|
|
|
153
|
+
| `userName` | `authState.loginUserData()?.name` | `'Guest'` |
|
|
154
|
+
| `userEmail` | `authState.loginUserData()?.email` | `''` |
|
|
155
|
+
| `profilePicture` | `layoutService.userProfilePictureUrl()` | `''` |
|
|
110
156
|
|
|
111
|
-
|
|
112
|
-
- Button shows current company name and branch name
|
|
113
|
-
- Popover with company/branch dropdowns
|
|
114
|
-
- Auto-selects branch if only one available
|
|
115
|
-
- Loading states and error handling
|
|
116
|
-
- Uses `LAYOUT_AUTH_API` for data fetching and switching
|
|
117
|
-
- Signal-based reactivity for button enable/disable state
|
|
157
|
+
### AppCompanyBranchSelector
|
|
118
158
|
|
|
119
|
-
|
|
120
|
-
1. Click company/branch button in topbar
|
|
121
|
-
2. Popover opens with company dropdown
|
|
122
|
-
3. Select company → Branches load
|
|
123
|
-
4. Select branch (required if branches exist)
|
|
124
|
-
5. Click "Switch" → Backend API call → JWT updated → Navigate to dashboard
|
|
159
|
+
Company/branch switcher in topbar. Shown when company feature is enabled.
|
|
125
160
|
|
|
126
|
-
**
|
|
127
|
-
|
|
128
|
-
|
|
161
|
+
**State Signals:**
|
|
162
|
+
| Signal | Description |
|
|
163
|
+
|--------|-------------|
|
|
164
|
+
| `companies` | Available companies list |
|
|
165
|
+
| `branches` | Branches for selected company |
|
|
166
|
+
| `selectedCompanyId` / `selectedBranchId` | Current selection |
|
|
167
|
+
| `isLoadingCompanies` / `isLoadingBranches` | Loading states |
|
|
168
|
+
| `isSwitching` | Switch operation in progress |
|
|
169
|
+
| `canSwitch` | Computed: enabled when selection differs from current |
|
|
129
170
|
|
|
130
|
-
|
|
171
|
+
**Workflow:**
|
|
172
|
+
1. Click button → Panel opens, companies load
|
|
173
|
+
2. Select company → Branches load (auto-select if single branch)
|
|
174
|
+
3. Select branch → Click "Switch" → API call → JWT updated → Redirect
|
|
131
175
|
|
|
132
|
-
|
|
176
|
+
### AppLauncher
|
|
133
177
|
|
|
134
|
-
|
|
135
|
-
- Responsive grid: 2 columns on mobile, 3 columns on desktop (`grid-cols-2 sm:grid-cols-3`)
|
|
136
|
-
- External links (open in new tabs)
|
|
137
|
-
- Icon support: PrimeNG, Material, or image URLs
|
|
138
|
-
- Permission filtering via `permissionLogic`
|
|
139
|
-
- Only visible when `layoutService.hasApps()` is true
|
|
140
|
-
- App names truncated to prevent overflow
|
|
178
|
+
App launcher grid for quick access to external applications.
|
|
141
179
|
|
|
142
|
-
**Configuration:**
|
|
143
180
|
```typescript
|
|
144
|
-
import { ILauncherApp } from '@flusys/ng-layout';
|
|
145
|
-
|
|
146
181
|
const apps: ILauncherApp[] = [
|
|
147
182
|
{
|
|
148
183
|
id: 'docs',
|
|
149
184
|
name: 'Docs',
|
|
150
|
-
iconType:
|
|
185
|
+
iconType: IconTypeEnum.PrimeNg,
|
|
151
186
|
icon: 'pi pi-book',
|
|
152
187
|
url: 'https://docs.example.com',
|
|
153
|
-
|
|
154
|
-
{
|
|
155
|
-
id: 'analytics',
|
|
156
|
-
name: 'Analytics',
|
|
157
|
-
iconType: 1,
|
|
158
|
-
icon: 'pi pi-chart-bar',
|
|
159
|
-
url: 'https://analytics.example.com',
|
|
160
|
-
permissionLogic: { type: 'action', actionId: 'analytics.view' },
|
|
188
|
+
permissionLogic: { id: 'docs', type: 'action', actionId: 'docs.view' }, // optional
|
|
161
189
|
},
|
|
162
190
|
];
|
|
163
|
-
|
|
164
|
-
// In app initialization:
|
|
165
191
|
layoutService.setApps(apps);
|
|
166
192
|
```
|
|
167
193
|
|
|
168
|
-
|
|
194
|
+
**Features:**
|
|
195
|
+
- Responsive grid: `grid-cols-2 sm:grid-cols-3`
|
|
196
|
+
- External links (new tab)
|
|
197
|
+
- Permission filtering via `permissionLogic`
|
|
198
|
+
- Only visible when `layoutService.hasApps()` is true
|
|
169
199
|
|
|
170
|
-
|
|
200
|
+
### AppConfigurator
|
|
171
201
|
|
|
172
|
-
|
|
173
|
-
- Primary color selector (16 colors + noir)
|
|
174
|
-
- Surface color selector (9 colors)
|
|
175
|
-
- Preset selector (Aura, Lara, Nora)
|
|
176
|
-
- Menu mode selector (static/overlay)
|
|
177
|
-
- Dynamic theme application via PrimeUI themes API
|
|
202
|
+
Theme customizer dropdown.
|
|
178
203
|
|
|
179
|
-
|
|
204
|
+
**Options:**
|
|
205
|
+
| Setting | Values |
|
|
206
|
+
|---------|--------|
|
|
207
|
+
| Primary colors | `emerald`, `green`, `lime`, `orange`, `amber`, `yellow`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`, `noir` |
|
|
208
|
+
| Surface colors | `slate`, `gray`, `zinc`, `neutral`, `stone`, `soho`, `viva`, `ocean` |
|
|
209
|
+
| Presets | `Aura` (default), `Lara`, `Nora` |
|
|
210
|
+
| Menu mode | `static`, `overlay` |
|
|
180
211
|
|
|
181
|
-
|
|
212
|
+
Uses `$t()`, `updatePreset()`, `updateSurfacePalette()` from `@primeuix/themes`.
|
|
182
213
|
|
|
183
|
-
###
|
|
214
|
+
### AppFloatingConfigurator
|
|
184
215
|
|
|
185
|
-
|
|
216
|
+
Fixed-position floating buttons for pages without AppLayout (e.g., auth pages).
|
|
186
217
|
|
|
187
218
|
```typescript
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
219
|
+
@Component({
|
|
220
|
+
imports: [AppFloatingConfigurator],
|
|
221
|
+
template: `<app-floating-configurator /><div class="login-form">...</div>`
|
|
222
|
+
})
|
|
223
|
+
export class LoginComponent {}
|
|
194
224
|
```
|
|
195
225
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
| Signal | Type | Description |
|
|
199
|
-
|--------|------|-------------|
|
|
200
|
-
| `layoutConfig` | `Signal<LayoutConfig>` | Theme/preset/menuMode configuration |
|
|
201
|
-
| `layoutState` | `Signal<LayoutState>` | UI state (menu active, sidebar visible) |
|
|
202
|
-
| `transitionComplete` | `Signal<boolean>` | Dark mode transition state |
|
|
203
|
-
| `userProfile` | `Signal<UserProfile \| null>` | Current user display data |
|
|
204
|
-
| `companyProfile` | `Signal<CompanyProfile \| null>` | Current company display data |
|
|
205
|
-
| `appName` | `Signal<string>` | App name for fallback display |
|
|
206
|
-
| `authorName` | `Signal<string>` | Author/company name for footer |
|
|
207
|
-
| `authorUrl` | `Signal<string>` | Author website URL for footer |
|
|
208
|
-
| `menu` | `Computed<IMenuItem[]>` | Permission-filtered menu items |
|
|
209
|
-
| `apps` | `Computed<ILauncherApp[]>` | Permission-filtered launcher apps |
|
|
226
|
+
Position: `top-4 md:top-8 right-2 md:right-8 z-50`
|
|
210
227
|
|
|
211
|
-
|
|
228
|
+
---
|
|
212
229
|
|
|
213
|
-
|
|
214
|
-
|--------|------|-------------|
|
|
215
|
-
| `isDarkTheme` | `Computed<boolean>` | Whether dark theme is active |
|
|
216
|
-
| `isSidebarActive` | `Computed<boolean>` | Whether sidebar/menu is visible |
|
|
217
|
-
| `isOverlay` | `Computed<boolean>` | Whether menu mode is overlay |
|
|
218
|
-
| `getPrimary` | `Computed<string>` | Current primary color |
|
|
219
|
-
| `getSurface` | `Computed<string \| null>` | Current surface color |
|
|
220
|
-
| `userName` | `Computed<string>` | User name or "User" fallback |
|
|
221
|
-
| `userEmail` | `Computed<string>` | User email |
|
|
222
|
-
| `userProfilePictureUrl` | `Computed<string \| null>` | Profile picture URL |
|
|
223
|
-
| `companyName` | `Computed<string>` | Company name or appName fallback |
|
|
224
|
-
| `companyLogoUrl` | `Computed<string \| null>` | Company logo URL |
|
|
225
|
-
| `isAuthenticated` | `Computed<boolean>` | Whether user profile exists |
|
|
226
|
-
| `hasApps` | `Computed<boolean>` | Whether filtered apps exist |
|
|
227
|
-
|
|
228
|
-
**Methods:**
|
|
230
|
+
## Services
|
|
229
231
|
|
|
230
|
-
|
|
231
|
-
|--------|-------------|
|
|
232
|
-
| `onMenuToggle()` | Toggle menu visibility (handles static/overlay/mobile) |
|
|
233
|
-
| `onConfigUpdate()` | Emit config change event |
|
|
234
|
-
| `onMenuStateChange(event)` | Emit menu item state change |
|
|
235
|
-
| `reset()` | Reset menu state |
|
|
236
|
-
| `setUserProfile(profile)` | Set user profile for layout display |
|
|
237
|
-
| `setCompanyProfile(profile)` | Set company profile for layout display |
|
|
238
|
-
| `setMenu(items)` | Set raw menu items (auto-filtered by permissions) |
|
|
239
|
-
| `clearMenu()` | Clear menu items |
|
|
240
|
-
| `setApps(apps)` | Set launcher apps (auto-filtered by permissions) |
|
|
241
|
-
| `clearApps()` | Clear launcher apps |
|
|
242
|
-
| `toggleDarkMode(config?)` | Toggle dark mode CSS class |
|
|
243
|
-
| `isDesktop()` | Check if viewport > 991px |
|
|
244
|
-
| `isMobile()` | Check if viewport <= 991px |
|
|
245
|
-
|
|
246
|
-
**RxJS Observables:**
|
|
247
|
-
|
|
248
|
-
| Observable | Description |
|
|
249
|
-
|------------|-------------|
|
|
250
|
-
| `menuSource$` | Menu state change events |
|
|
251
|
-
| `resetSource$` | Menu reset events |
|
|
252
|
-
| `configUpdate$` | Config change events |
|
|
253
|
-
| `overlayOpen$` | Menu overlay opened |
|
|
232
|
+
### LayoutService
|
|
254
233
|
|
|
255
|
-
|
|
234
|
+
Signal-based singleton service for layout state management.
|
|
256
235
|
|
|
257
|
-
|
|
236
|
+
#### Interfaces
|
|
258
237
|
|
|
259
|
-
**localStorage Key:** `flusys.layout.config`
|
|
260
|
-
|
|
261
|
-
**Persisted Configuration (LayoutConfig):**
|
|
262
238
|
```typescript
|
|
263
239
|
interface LayoutConfig {
|
|
264
240
|
preset?: string; // "Aura" | "Lara" | "Nora"
|
|
265
|
-
primary?: string; // "emerald" | "green" |
|
|
266
|
-
surface?: string | null; // "slate" | "gray" |
|
|
241
|
+
primary?: string; // "emerald" | "green" | etc.
|
|
242
|
+
surface?: string | null; // "slate" | "gray" | etc.
|
|
267
243
|
darkTheme?: boolean;
|
|
268
244
|
menuMode?: 'static' | 'overlay';
|
|
269
245
|
}
|
|
270
|
-
```
|
|
271
246
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
- Version mismatches trigger automatic clearing
|
|
279
|
-
|
|
280
|
-
**Clearing Saved Configuration:**
|
|
281
|
-
```typescript
|
|
282
|
-
import { LayoutPersistenceService } from '@flusys/ng-layout';
|
|
247
|
+
interface LayoutState {
|
|
248
|
+
staticMenuDesktopInactive?: boolean;
|
|
249
|
+
overlayMenuActive?: boolean;
|
|
250
|
+
staticMenuMobileActive?: boolean;
|
|
251
|
+
menuHoverActive?: boolean;
|
|
252
|
+
}
|
|
283
253
|
|
|
284
|
-
|
|
254
|
+
interface UserProfile { id: string; name: string; email: string; profilePictureUrl?: string | null; }
|
|
255
|
+
interface CompanyProfile { id: string; name: string; slug: string; logoUrl?: string | null; }
|
|
285
256
|
```
|
|
286
257
|
|
|
287
|
-
|
|
258
|
+
#### Signals
|
|
288
259
|
|
|
289
|
-
|
|
260
|
+
**State Signals (readonly):**
|
|
261
|
+
| Signal | Type |
|
|
262
|
+
|--------|------|
|
|
263
|
+
| `layoutConfig` | `Signal<LayoutConfig>` |
|
|
264
|
+
| `layoutState` | `Signal<LayoutState>` |
|
|
265
|
+
| `transitionComplete` | `Signal<boolean>` |
|
|
266
|
+
| `userProfile` | `Signal<UserProfile \| null>` |
|
|
267
|
+
| `companyProfile` | `Signal<CompanyProfile \| null>` |
|
|
290
268
|
|
|
291
|
-
**
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}));
|
|
298
|
-
```
|
|
269
|
+
**Static Values (from APP_CONFIG):**
|
|
270
|
+
| Property | Fallback |
|
|
271
|
+
|----------|----------|
|
|
272
|
+
| `appName` | `DEFAULT_APP_NAME` |
|
|
273
|
+
| `authorName` | `DEFAULT_AUTHOR.name` |
|
|
274
|
+
| `authorUrl` | `DEFAULT_AUTHOR.url` |
|
|
299
275
|
|
|
300
|
-
**
|
|
276
|
+
**Computed Signals:**
|
|
277
|
+
| Signal | Description |
|
|
278
|
+
|--------|-------------|
|
|
279
|
+
| `menu` | Permission-filtered menu items |
|
|
280
|
+
| `apps` | Permission-filtered launcher apps |
|
|
281
|
+
| `hasApps` | Whether apps exist |
|
|
282
|
+
| `isDarkTheme` | Dark theme active |
|
|
283
|
+
| `isSidebarActive` | Sidebar visible |
|
|
284
|
+
| `isOverlay` | Overlay menu mode |
|
|
285
|
+
| `getPrimary` / `getSurface` | Current colors |
|
|
286
|
+
| `userName` / `userEmail` | User info (with fallbacks) |
|
|
287
|
+
| `userProfilePictureUrl` / `companyLogoUrl` | Image URLs |
|
|
288
|
+
| `companyName` | Company name or appName fallback |
|
|
289
|
+
| `isAuthenticated` | User profile exists |
|
|
290
|
+
|
|
291
|
+
#### Methods
|
|
292
|
+
|
|
293
|
+
| Method | Description |
|
|
294
|
+
|--------|-------------|
|
|
295
|
+
| `onMenuToggle()` | Toggle menu (handles static/overlay/mobile) |
|
|
296
|
+
| `onMenuStateChange(event)` | Emit menu state change |
|
|
297
|
+
| `reset()` | Reset menu state |
|
|
298
|
+
| `isDesktop()` / `isMobile()` | Viewport checks (SSR-safe) |
|
|
299
|
+
| `updateLayoutConfig(config)` | Partial config update |
|
|
300
|
+
| `updateLayoutState(state)` | Partial state update |
|
|
301
|
+
| `toggleDarkMode(config?)` | Toggle `app-dark` class |
|
|
302
|
+
| `setUserProfile(profile)` | Set user profile |
|
|
303
|
+
| `setCompanyProfile(profile)` | Set company profile |
|
|
304
|
+
| `setMenu(items)` / `clearMenu()` | Menu management |
|
|
305
|
+
| `setApps(apps)` / `clearApps()` | Apps management |
|
|
306
|
+
|
|
307
|
+
#### RxJS Observables
|
|
308
|
+
|
|
309
|
+
| Observable | Purpose |
|
|
310
|
+
|------------|---------|
|
|
311
|
+
| `menuSource$` | Menu state change events |
|
|
312
|
+
| `resetSource$` | Menu reset events |
|
|
313
|
+
| `configUpdate$` | Config change events |
|
|
314
|
+
| `overlayOpen$` | Menu overlay opened |
|
|
315
|
+
|
|
316
|
+
#### Dark Mode Transition
|
|
317
|
+
|
|
318
|
+
Uses View Transitions API with fallback:
|
|
301
319
|
```typescript
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
export class AppComponent {
|
|
306
|
-
readonly layoutService = inject(LayoutService);
|
|
320
|
+
toggleDarkMode(config?: LayoutConfig): void {
|
|
321
|
+
const isDark = (config ?? this._layoutConfig()).darkTheme;
|
|
322
|
+
this.document.documentElement.classList.toggle('app-dark', isDark);
|
|
307
323
|
}
|
|
308
324
|
```
|
|
309
325
|
|
|
310
|
-
|
|
326
|
+
### LayoutPersistenceService
|
|
311
327
|
|
|
312
|
-
|
|
313
|
-
|-------|------|---------------|
|
|
314
|
-
| `GreenTheme` | Material | `#01712c` |
|
|
315
|
-
| `NavyBlueTheme` | Material | `#3535cd` |
|
|
328
|
+
Persists layout configuration to localStorage.
|
|
316
329
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
330
|
+
| Method | Description |
|
|
331
|
+
|--------|-------------|
|
|
332
|
+
| `load()` | Load and validate saved config |
|
|
333
|
+
| `save(config)` | Save config with version |
|
|
334
|
+
| `clear()` | Remove saved config |
|
|
335
|
+
|
|
336
|
+
**localStorage Key:** `flusys.layout.config`
|
|
320
337
|
|
|
321
|
-
|
|
338
|
+
**Validation:**
|
|
339
|
+
- Invalid preset → `'Aura'`
|
|
340
|
+
- Invalid menuMode → `'static'`
|
|
341
|
+
- Non-boolean darkTheme → `false`
|
|
342
|
+
- Version mismatch or invalid JSON → Clear and return null
|
|
322
343
|
|
|
323
|
-
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Auth Integration
|
|
324
347
|
|
|
325
|
-
|
|
348
|
+
Layout components use injection tokens for auth data without direct ng-auth dependency.
|
|
326
349
|
|
|
327
|
-
|
|
350
|
+
### Tokens
|
|
328
351
|
|
|
329
352
|
```typescript
|
|
353
|
+
// LAYOUT_AUTH_STATE - Provides current user/company/branch state
|
|
330
354
|
interface ILayoutAuthState {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
355
|
+
currentCompanyInfo: Signal<ICompanyInfo | null>;
|
|
356
|
+
currentBranchInfo: Signal<IBranchInfo | null>;
|
|
357
|
+
loginUserData: Signal<IUserInfo | null>;
|
|
334
358
|
}
|
|
335
|
-
```
|
|
336
359
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
Provides auth actions (logout, company switching) to layout components.
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
360
|
+
// LAYOUT_AUTH_API - Provides auth actions
|
|
342
361
|
interface ILayoutAuthApi {
|
|
343
362
|
logOut(): Observable<any>;
|
|
344
363
|
navigateLogin(withUrl?: boolean): void;
|
|
@@ -348,7 +367,9 @@ interface ILayoutAuthApi {
|
|
|
348
367
|
}
|
|
349
368
|
```
|
|
350
369
|
|
|
351
|
-
|
|
370
|
+
**Related:** `ICompanyInfo` (id, name, slug, imageId), `IBranchInfo` (id, name, slug, imageId?), `IUserInfo` (id, name, email, profilePicture?)
|
|
371
|
+
|
|
372
|
+
### Configuration
|
|
352
373
|
|
|
353
374
|
```typescript
|
|
354
375
|
// app.config.ts
|
|
@@ -361,53 +382,48 @@ providers: [
|
|
|
361
382
|
]
|
|
362
383
|
```
|
|
363
384
|
|
|
364
|
-
Components inject
|
|
385
|
+
Components inject with `{ optional: true }` for graceful degradation without auth.
|
|
365
386
|
|
|
366
|
-
|
|
387
|
+
---
|
|
367
388
|
|
|
368
|
-
|
|
389
|
+
## Permission-Based Filtering
|
|
369
390
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
391
|
+
Menu items and launcher apps are automatically filtered via `permissionLogic`.
|
|
392
|
+
|
|
393
|
+
### How It Works
|
|
394
|
+
|
|
395
|
+
1. Set raw items via `layoutService.setMenu(items)` or `setApps(apps)`
|
|
396
|
+
2. `LayoutService.menu` / `apps` are computed signals that filter via utilities
|
|
397
|
+
3. Uses `evaluateLogicNode()` from ng-shared
|
|
398
|
+
|
|
399
|
+
### Interfaces
|
|
375
400
|
|
|
376
|
-
**ILogicNode Types:**
|
|
377
401
|
```typescript
|
|
402
|
+
interface IMenuItem {
|
|
403
|
+
id?: string; label?: string; icon?: string; iconType?: number;
|
|
404
|
+
routerLink?: string[]; children?: IMenuItem[]; separator?: boolean;
|
|
405
|
+
permissionLogic?: ILogicNode | null;
|
|
406
|
+
}
|
|
407
|
+
|
|
378
408
|
interface ILogicNode {
|
|
379
409
|
id: string;
|
|
380
410
|
type: 'group' | 'action' | 'role';
|
|
381
|
-
operator?: 'AND' | 'OR'; //
|
|
382
|
-
actionId?: string; //
|
|
383
|
-
roleId?: string; //
|
|
384
|
-
children?: ILogicNode[]; //
|
|
411
|
+
operator?: 'AND' | 'OR'; // For 'group'
|
|
412
|
+
actionId?: string; // For 'action'
|
|
413
|
+
roleId?: string; // For 'role'
|
|
414
|
+
children?: ILogicNode[]; // For 'group'
|
|
385
415
|
}
|
|
386
416
|
```
|
|
387
417
|
|
|
388
|
-
|
|
418
|
+
### Examples
|
|
389
419
|
|
|
390
420
|
```typescript
|
|
391
|
-
//
|
|
392
|
-
{
|
|
393
|
-
id: 'administration',
|
|
394
|
-
label: 'Administration',
|
|
395
|
-
icon: 'pi pi-cog',
|
|
396
|
-
permissionLogic: { id: 'admin-check', type: 'action', actionId: 'admin.access' },
|
|
397
|
-
children: [
|
|
398
|
-
{ id: 'users', label: 'Users', routerLink: ['/admin/users'] },
|
|
399
|
-
{ id: 'settings', label: 'Settings', routerLink: ['/admin/settings'] },
|
|
400
|
-
],
|
|
401
|
-
}
|
|
402
|
-
```
|
|
421
|
+
// Single action check
|
|
422
|
+
permissionLogic: { id: '1', type: 'action', actionId: 'admin.access' }
|
|
403
423
|
|
|
404
|
-
**Complex permission examples:**
|
|
405
|
-
```typescript
|
|
406
424
|
// OR - user needs ANY permission
|
|
407
425
|
permissionLogic: {
|
|
408
|
-
id: '
|
|
409
|
-
type: 'group',
|
|
410
|
-
operator: 'OR',
|
|
426
|
+
id: 'check', type: 'group', operator: 'OR',
|
|
411
427
|
children: [
|
|
412
428
|
{ id: '1', type: 'action', actionId: 'reports.view' },
|
|
413
429
|
{ id: '2', type: 'role', roleId: 'manager-role-id' },
|
|
@@ -416,9 +432,7 @@ permissionLogic: {
|
|
|
416
432
|
|
|
417
433
|
// AND - user needs ALL permissions
|
|
418
434
|
permissionLogic: {
|
|
419
|
-
id: '
|
|
420
|
-
type: 'group',
|
|
421
|
-
operator: 'AND',
|
|
435
|
+
id: 'check', type: 'group', operator: 'AND',
|
|
422
436
|
children: [
|
|
423
437
|
{ id: '1', type: 'action', actionId: 'admin.access' },
|
|
424
438
|
{ id: '2', type: 'role', roleId: 'superuser-role-id' },
|
|
@@ -426,37 +440,37 @@ permissionLogic: {
|
|
|
426
440
|
}
|
|
427
441
|
```
|
|
428
442
|
|
|
429
|
-
|
|
443
|
+
### Filtering Behavior
|
|
444
|
+
|
|
445
|
+
- **Separators** pass through without permission checks
|
|
446
|
+
- **Parent items** with no visible children are hidden
|
|
447
|
+
- **Children** filtered recursively
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Pre-defined Themes
|
|
430
452
|
|
|
431
|
-
|
|
453
|
+
Custom Material-based themes using `definePreset`:
|
|
432
454
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
455
|
+
| Theme | Primary Color | Hover Color |
|
|
456
|
+
|-------|---------------|-------------|
|
|
457
|
+
| `GreenTheme` | `#01712c` | `#119744` |
|
|
458
|
+
| `NavyBlueTheme` | `#3535cd` | `#0707a9` |
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
import { GreenTheme, NavyBlueTheme } from '@flusys/ng-layout';
|
|
439
462
|
```
|
|
440
463
|
|
|
464
|
+
---
|
|
465
|
+
|
|
441
466
|
## Usage Examples
|
|
442
467
|
|
|
443
|
-
### Basic
|
|
468
|
+
### Basic Setup
|
|
444
469
|
|
|
445
470
|
```typescript
|
|
446
|
-
// app.component.ts
|
|
447
|
-
import { Component, inject } from '@angular/core';
|
|
448
|
-
import { RouterOutlet } from '@angular/router';
|
|
449
|
-
import { AppLayout, LayoutService } from '@flusys/ng-layout';
|
|
450
|
-
|
|
451
471
|
@Component({
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
imports: [RouterOutlet, AppLayout],
|
|
455
|
-
template: `
|
|
456
|
-
<app-layout>
|
|
457
|
-
<router-outlet />
|
|
458
|
-
</app-layout>
|
|
459
|
-
`,
|
|
472
|
+
imports: [AppLayout],
|
|
473
|
+
template: `<app-layout><router-outlet /></app-layout>`,
|
|
460
474
|
host: { '[class.app-dark]': 'layoutService.isDarkTheme()' },
|
|
461
475
|
})
|
|
462
476
|
export class AppComponent {
|
|
@@ -467,246 +481,142 @@ export class AppComponent {
|
|
|
467
481
|
### Menu Configuration
|
|
468
482
|
|
|
469
483
|
```typescript
|
|
470
|
-
import { LayoutService, IMenuItem } from '@flusys/ng-layout';
|
|
471
|
-
|
|
472
484
|
const menuItems: IMenuItem[] = [
|
|
485
|
+
{ id: 'dashboard', label: 'Dashboard', icon: 'pi pi-home', routerLink: ['/dashboard'] },
|
|
473
486
|
{
|
|
474
|
-
label: '
|
|
475
|
-
|
|
476
|
-
routerLink: ['/dashboard'],
|
|
477
|
-
},
|
|
478
|
-
{
|
|
479
|
-
label: 'Management',
|
|
480
|
-
icon: 'pi pi-cog',
|
|
487
|
+
id: 'admin', label: 'Admin', icon: 'pi pi-cog',
|
|
488
|
+
permissionLogic: { id: 'admin', type: 'action', actionId: 'admin.access' },
|
|
481
489
|
children: [
|
|
482
|
-
{ label: 'Users', icon: 'pi pi-users', routerLink: ['/users'] },
|
|
483
|
-
{ label: 'Roles', icon: 'pi pi-shield', routerLink: ['/roles'] },
|
|
490
|
+
{ id: 'users', label: 'Users', icon: 'pi pi-users', routerLink: ['/admin/users'] },
|
|
491
|
+
{ id: 'roles', label: 'Roles', icon: 'pi pi-shield', routerLink: ['/admin/roles'] },
|
|
484
492
|
],
|
|
485
493
|
},
|
|
486
494
|
{ separator: true },
|
|
487
|
-
{
|
|
488
|
-
label: 'Settings',
|
|
489
|
-
icon: 'pi pi-cog',
|
|
490
|
-
routerLink: ['/settings'],
|
|
491
|
-
},
|
|
495
|
+
{ id: 'settings', label: 'Settings', icon: 'pi pi-cog', routerLink: ['/settings'] },
|
|
492
496
|
];
|
|
493
|
-
|
|
494
497
|
layoutService.setMenu(menuItems);
|
|
495
498
|
```
|
|
496
499
|
|
|
497
|
-
###
|
|
500
|
+
### Profile Setup
|
|
498
501
|
|
|
499
502
|
```typescript
|
|
500
|
-
//
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
503
|
+
// After login
|
|
504
|
+
layoutService.setUserProfile({
|
|
505
|
+
id: 'user-123',
|
|
506
|
+
name: 'John Doe',
|
|
507
|
+
email: 'john@example.com',
|
|
508
|
+
profilePictureUrl: 'https://example.com/avatar.jpg',
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
layoutService.setCompanyProfile({
|
|
512
|
+
id: 'company-456',
|
|
513
|
+
name: 'Acme Corp',
|
|
514
|
+
slug: 'acme-corp',
|
|
515
|
+
logoUrl: 'https://example.com/logo.png',
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// On logout
|
|
519
|
+
layoutService.setUserProfile(null);
|
|
520
|
+
layoutService.setCompanyProfile(null);
|
|
510
521
|
```
|
|
511
522
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
```typescript
|
|
515
|
-
import { LayoutService } from '@flusys/ng-layout';
|
|
516
|
-
import { MenuApiService } from './services/menu-api.service';
|
|
517
|
-
|
|
518
|
-
@Component({...})
|
|
519
|
-
export class AppComponent implements OnInit {
|
|
520
|
-
private readonly layoutService = inject(LayoutService);
|
|
521
|
-
private readonly menuApi = inject(MenuApiService);
|
|
522
|
-
|
|
523
|
-
async ngOnInit() {
|
|
524
|
-
const response = await firstValueFrom(this.menuApi.getUserMenu());
|
|
525
|
-
if (response.success) {
|
|
526
|
-
this.layoutService.setMenu(response.data);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
```
|
|
523
|
+
---
|
|
531
524
|
|
|
532
525
|
## Best Practices
|
|
533
526
|
|
|
534
|
-
###
|
|
535
|
-
- Use `AppLayout` as root layout component
|
|
536
|
-
- Set menu items via `layoutService.setMenu()` during app initialization
|
|
537
|
-
- Use `LayoutService` for all layout state changes
|
|
538
|
-
- Don't manipulate DOM directly for layout changes
|
|
539
|
-
|
|
540
|
-
### Menu Management
|
|
541
|
-
- Load menus dynamically from backend (IAM module)
|
|
542
|
-
- Group related items under parent menu items with `permissionLogic`
|
|
543
|
-
- Use PrimeIcons for consistency
|
|
544
|
-
- Use `children` property for nested items (not `items`)
|
|
545
|
-
|
|
546
|
-
### Auth Integration
|
|
547
|
-
- Use injection tokens (`LAYOUT_AUTH_STATE`, `LAYOUT_AUTH_API`)
|
|
548
|
-
- Don't import ng-auth directly in ng-layout
|
|
549
|
-
- Provide adapters at app level in `app.config.ts`
|
|
550
|
-
- Layout works without auth (graceful degradation)
|
|
551
|
-
|
|
552
|
-
### Theme Management
|
|
553
|
-
- Use CSS animations, not `@angular/animations`
|
|
554
|
-
- Apply `app-dark` class to root element via host binding
|
|
555
|
-
- Theme preferences persist automatically to localStorage
|
|
556
|
-
- Test both light and dark themes for contrast
|
|
557
|
-
|
|
558
|
-
**Dark Mode CSS Variables:**
|
|
559
|
-
All components use PrimeNG CSS variables for automatic dark mode support:
|
|
560
|
-
```css
|
|
561
|
-
/* Backgrounds */
|
|
562
|
-
background-color: var(--surface-overlay); /* Dropdown panels */
|
|
563
|
-
bg-emphasis /* Hover states */
|
|
564
|
-
|
|
565
|
-
/* Text */
|
|
566
|
-
text-color /* Primary text */
|
|
567
|
-
text-muted-color /* Secondary text */
|
|
527
|
+
### Signal Patterns
|
|
568
528
|
|
|
569
|
-
|
|
570
|
-
|
|
529
|
+
Use private writable + public readonly pattern:
|
|
530
|
+
```typescript
|
|
531
|
+
private readonly _isActive = signal(false);
|
|
532
|
+
readonly isActive = this._isActive.asReadonly();
|
|
571
533
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
534
|
+
// Update via private signal
|
|
535
|
+
this._isActive.set(true);
|
|
536
|
+
this._isActive.update(v => !v);
|
|
575
537
|
```
|
|
576
538
|
|
|
577
|
-
### Responsive
|
|
578
|
-
- Use Tailwind for responsive utilities
|
|
579
|
-
- Layout automatically handles responsive behavior (static → overlay on mobile)
|
|
580
|
-
- Menu closes on navigation and outside clicks
|
|
581
|
-
|
|
582
|
-
**Dropdown Panel Pattern:**
|
|
583
|
-
```css
|
|
584
|
-
/* Full width on mobile, auto-width on desktop with min/max constraints */
|
|
585
|
-
class="w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
|
|
586
|
-
```
|
|
539
|
+
### Responsive Widths
|
|
587
540
|
|
|
588
|
-
**Component Responsive Widths:**
|
|
589
541
|
| Component | Pattern |
|
|
590
542
|
|-----------|---------|
|
|
591
|
-
|
|
|
592
|
-
|
|
|
593
|
-
|
|
|
594
|
-
|
|
|
595
|
-
|
|
543
|
+
| AppConfigurator | `w-[calc(100vw-2rem)] sm:w-72 max-w-72` |
|
|
544
|
+
| AppProfile | `w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]` |
|
|
545
|
+
| AppCompanyBranchSelector | `w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[300px] max-w-[360px]` |
|
|
546
|
+
| AppLauncher | `w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]` |
|
|
547
|
+
|
|
548
|
+
### Dark Mode CSS Variables
|
|
596
549
|
|
|
597
|
-
**Grid Responsiveness:**
|
|
598
550
|
```css
|
|
599
|
-
/*
|
|
600
|
-
|
|
551
|
+
background-color: var(--surface-overlay); /* Panels */
|
|
552
|
+
bg-emphasis /* Hover states */
|
|
553
|
+
text-color / text-muted-color /* Text */
|
|
554
|
+
border-surface /* Borders */
|
|
555
|
+
bg-primary / text-primary-contrast /* Primary */
|
|
601
556
|
```
|
|
602
557
|
|
|
603
|
-
|
|
604
|
-
```html
|
|
605
|
-
<!-- Prevent overflow on long names/emails -->
|
|
606
|
-
<div class="flex flex-col min-w-0 flex-1">
|
|
607
|
-
<span class="truncate">{{ userName() }}</span>
|
|
608
|
-
</div>
|
|
609
|
-
```
|
|
558
|
+
---
|
|
610
559
|
|
|
611
560
|
## Common Issues
|
|
612
561
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
Provide `LAYOUT_AUTH_STATE` in `app.config.ts`:
|
|
623
|
-
```typescript
|
|
624
|
-
{ provide: LAYOUT_AUTH_STATE, useExisting: AuthLayoutStateAdapter }
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
### Theme Not Applying
|
|
628
|
-
|
|
629
|
-
Add host binding to root component:
|
|
630
|
-
```typescript
|
|
631
|
-
@Component({
|
|
632
|
-
host: { '[class.app-dark]': 'layoutService.isDarkTheme()' }
|
|
633
|
-
})
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
### Sidebar Not Responsive
|
|
562
|
+
| Issue | Solution |
|
|
563
|
+
|-------|----------|
|
|
564
|
+
| Menu not showing | Call `layoutService.setMenu([...])` |
|
|
565
|
+
| Auth info missing | Provide `LAYOUT_AUTH_STATE` token |
|
|
566
|
+
| Theme not applying | Add `host: { '[class.app-dark]': 'layoutService.isDarkTheme()' }` |
|
|
567
|
+
| Menu items hidden | Check `permissionLogic` and user permissions |
|
|
568
|
+
| Profile picture missing | Include `profilePictureUrl` in `setUserProfile()` |
|
|
569
|
+
| Company feature missing | Enable `services.auth.enabled: true` and provide both auth tokens |
|
|
637
570
|
|
|
638
|
-
|
|
571
|
+
---
|
|
639
572
|
|
|
640
573
|
## API Reference
|
|
641
574
|
|
|
642
575
|
### Components
|
|
643
576
|
|
|
644
|
-
| Component | Selector |
|
|
645
|
-
|
|
646
|
-
| `AppLayout` | `app-layout` |
|
|
647
|
-
| `AppTopbar` | `app-topbar` |
|
|
648
|
-
| `AppSidebar` | `app-sidebar` |
|
|
649
|
-
| `AppMenu` | `app-menu` |
|
|
650
|
-
| `AppMenuitem` | `[app-menuitem]` |
|
|
651
|
-
| `AppFooter` | `app-footer` |
|
|
652
|
-
| `AppProfile` | `app-profile` |
|
|
653
|
-
| `AppCompanyBranchSelector` | `app-company-branch-selector` |
|
|
654
|
-
| `AppLauncher` | `app-launcher` |
|
|
655
|
-
| `AppConfigurator` | `app-configurator` |
|
|
656
|
-
| `AppFloatingConfigurator` | `app-floating-configurator` |
|
|
577
|
+
| Component | Selector |
|
|
578
|
+
|-----------|----------|
|
|
579
|
+
| `AppLayout` | `app-layout` |
|
|
580
|
+
| `AppTopbar` | `app-topbar` |
|
|
581
|
+
| `AppSidebar` | `app-sidebar` |
|
|
582
|
+
| `AppMenu` | `app-menu` |
|
|
583
|
+
| `AppMenuitem` | `[app-menuitem]` |
|
|
584
|
+
| `AppFooter` | `app-footer` |
|
|
585
|
+
| `AppProfile` | `app-profile` |
|
|
586
|
+
| `AppCompanyBranchSelector` | `app-company-branch-selector` |
|
|
587
|
+
| `AppLauncher` | `app-launcher` |
|
|
588
|
+
| `AppConfigurator` | `app-configurator` |
|
|
589
|
+
| `AppFloatingConfigurator` | `app-floating-configurator` |
|
|
657
590
|
|
|
658
591
|
### Services
|
|
659
592
|
|
|
660
593
|
| Service | Description |
|
|
661
594
|
|---------|-------------|
|
|
662
595
|
| `LayoutService` | Signal-based layout state management |
|
|
663
|
-
| `LayoutPersistenceService` | localStorage persistence
|
|
664
|
-
|
|
665
|
-
### Injection Tokens
|
|
596
|
+
| `LayoutPersistenceService` | localStorage persistence |
|
|
666
597
|
|
|
667
|
-
|
|
668
|
-
|-------|-----------|-------------|
|
|
669
|
-
| `LAYOUT_AUTH_STATE` | `ILayoutAuthState` | Auth state signals |
|
|
670
|
-
| `LAYOUT_AUTH_API` | `ILayoutAuthApi` | Auth actions |
|
|
598
|
+
### Tokens
|
|
671
599
|
|
|
672
|
-
|
|
600
|
+
| Token | Interface |
|
|
601
|
+
|-------|-----------|
|
|
602
|
+
| `LAYOUT_AUTH_STATE` | `ILayoutAuthState` |
|
|
603
|
+
| `LAYOUT_AUTH_API` | `ILayoutAuthApi` |
|
|
673
604
|
|
|
674
|
-
|
|
675
|
-
|-----------|-------------|
|
|
676
|
-
| `IMenuItem` | Menu item configuration |
|
|
677
|
-
| `ILauncherApp` | App launcher item configuration |
|
|
678
|
-
| `ILayoutAuthState` | Auth state for layout |
|
|
679
|
-
| `ILayoutAuthApi` | Auth actions for layout |
|
|
680
|
-
| `ICompanyInfo` | Company information |
|
|
681
|
-
| `IBranchInfo` | Branch information |
|
|
682
|
-
| `IUserInfo` | User information |
|
|
683
|
-
| `LayoutConfig` | Theme/preset configuration |
|
|
684
|
-
| `LayoutState` | UI state (menu active, etc.) |
|
|
685
|
-
| `UserProfile` | User display data |
|
|
686
|
-
| `CompanyProfile` | Company display data |
|
|
687
|
-
| `MenuChangeEvent` | Menu state change event |
|
|
688
|
-
|
|
689
|
-
### Utility Functions
|
|
605
|
+
### Utilities
|
|
690
606
|
|
|
691
607
|
| Function | Description |
|
|
692
608
|
|----------|-------------|
|
|
693
|
-
| `filterMenuByPermissions(
|
|
694
|
-
| `filterAppsByPermissions(
|
|
609
|
+
| `filterMenuByPermissions()` | Filter menu items by permission logic |
|
|
610
|
+
| `filterAppsByPermissions()` | Filter launcher apps by permission logic |
|
|
695
611
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
| Export | Base Preset | Primary Color |
|
|
699
|
-
|--------|-------------|---------------|
|
|
700
|
-
| `GreenTheme` | Material | `#01712c` |
|
|
701
|
-
| `NavyBlueTheme` | Material | `#3535cd` |
|
|
612
|
+
---
|
|
702
613
|
|
|
703
614
|
## See Also
|
|
704
615
|
|
|
705
|
-
-
|
|
706
|
-
-
|
|
707
|
-
-
|
|
616
|
+
- [CORE-GUIDE.md](./CORE-GUIDE.md) - Configuration, interceptors
|
|
617
|
+
- [SHARED-GUIDE.md](./SHARED-GUIDE.md) - Shared components, provider interfaces
|
|
618
|
+
- [AUTH-GUIDE.md](./AUTH-GUIDE.md) - Authentication integration, adapters
|
|
708
619
|
|
|
709
620
|
---
|
|
710
621
|
|
|
711
|
-
**Last Updated:** 2026-02-21
|
|
712
|
-
**Angular Version:** 21
|
|
622
|
+
**Last Updated:** 2026-02-25 | **Version:** 3.0.0 | **Angular:** 21
|