@flusys/ng-layout 4.0.0-rc → 4.0.2

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 CHANGED
@@ -11,7 +11,7 @@
11
11
  | Property | Value |
12
12
  |----------|-------|
13
13
  | Package | `@flusys/ng-layout` |
14
- | Version | 4.0.0-rc |
14
+ | Version | 4.0.2 |
15
15
  | Dependencies | ng-core, ng-shared |
16
16
  | Build | `npm run build:ng-layout` |
17
17
 
@@ -73,6 +73,7 @@ export class AppComponent {
73
73
  **Layout Modes:**
74
74
  - **Static** - Sidebar always visible on desktop, overlay on mobile
75
75
  - **Overlay** - Sidebar overlays content when opened
76
+ - **Topbar** - Horizontal menu bar at the top for desktop, sidebar on mobile
76
77
 
77
78
  **Dynamic CSS Classes:**
78
79
  | Class | Condition |
@@ -90,18 +91,27 @@ export class AppComponent {
90
91
 
91
92
  ### AppTopbar
92
93
 
93
- Application header with branding and user actions.
94
+ Application header with branding and user actions. Supports different layouts via `menuMode`.
94
95
 
96
+ **Static/Overlay Modes:**
95
97
  ```
96
98
  ┌─────────────────────────────────────────────────────────────────┐
97
99
  │ [≡] CompanyName [☀/🌙] [🎨] [...] [Apps] [🏢] [👤] │
98
100
  └─────────────────────────────────────────────────────────────────┘
99
101
  ```
100
102
 
103
+ **Topbar Mode (Desktop):**
104
+ ```
105
+ ┌───────────────────────────────────────────────────────────────────────────────┐
106
+ │ CompanyName [Dashboard] [Admin] [IAM] [Storage] [☀/🌙] [🎨] [Apps] [🏢] [👤]│
107
+ └───────────────────────────────────────────────────────────────────────────────┘
108
+ ```
109
+
101
110
  | Element | Description |
102
111
  |---------|-------------|
103
- | `[≡]` | Menu toggle `layoutService.onMenuToggle()` |
112
+ | `[≡]` | Menu toggle (Static/Overlay on mobile, Topbar on desktop) |
104
113
  | CompanyName | From `layoutService.companyName` → links to `/` |
114
+ | Menu Items | In topbar mode, rendered horizontally on desktop |
105
115
  | `[☀/🌙]` | Dark mode toggle |
106
116
  | `[🎨]` | AppConfigurator panel |
107
117
  | `[...]` | Mobile menu (hidden on desktop) |
@@ -109,6 +119,11 @@ Application header with branding and user actions.
109
119
  | `[🏢]` | AppCompanyBranchSelector (if company feature enabled) |
110
120
  | `[👤]` | AppProfile dropdown |
111
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
+
112
127
  ### AppMenu & AppMenuitem
113
128
 
114
129
  Menu container renders permission-filtered items from `layoutService.menu`.
@@ -133,6 +148,75 @@ Menu container renders permission-filtered items from `layoutService.menu`.
133
148
  :host ul.submenu-expanded { max-height: 1000px; }
134
149
  ```
135
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
+ ---
159
+
160
+ ## Topbar Mode (v4.0.1)
161
+
162
+ Horizontal navigation bar at the top of the page, replacing traditional sidebar on desktop.
163
+
164
+ ### Features
165
+
166
+ - **Desktop:** Horizontal menu items with hover-triggered submenus
167
+ - **Mobile:** Falls back to traditional vertical sidebar
168
+ - **Responsive:** Auto-switches between topbar and sidebar based on viewport
169
+ - **Submenus:** Positioned below parent items, auto-adjusted to viewport
170
+ - **Persistence:** Menu visibility state persists in `layoutService.layoutState().topbarMenuVisible`
171
+
172
+ ### Layout Behavior
173
+
174
+ **Desktop (Topbar Mode):**
175
+ - Menu items rendered horizontally next to company name
176
+ - Hovering over items with children shows submenu
177
+ - No sidebar visible
178
+ - Menu toggle only appears on mobile
179
+
180
+ **Mobile (Topbar Mode):**
181
+ - Menu items rendered in vertical sidebar (same as overlay mode)
182
+ - Topbar shows menu toggle button
183
+ - Sidebar overlays content when open
184
+
185
+ ### Configuration
186
+
187
+ ```typescript
188
+ // Set menu mode to topbar
189
+ layoutService.updateLayoutConfig({ menuMode: 'topbar' });
190
+
191
+ // Or persist via LayoutPersistenceService
192
+ layoutPersistenceService.save({
193
+ preset: 'Aura',
194
+ primary: 'blue',
195
+ menuMode: 'topbar',
196
+ darkTheme: false,
197
+ });
198
+ ```
199
+
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
+ ---
219
+
136
220
  ### AppFooter
137
221
 
138
222
  Displays branding from `LayoutService`: `appName`, `authorName`, `authorUrl`
@@ -144,7 +228,7 @@ User profile dropdown in topbar.
144
228
  **Features:**
145
229
  - User info (picture, name, email) with text truncation
146
230
  - Profile link → `/profile`
147
- - Copy SignUp Link → `{origin}/auth/register?companySlug={slug}`
231
+ - Copy SignUp Link → `{origin}/auth/register?companySlug={slug}` (hidden if sign-up disabled)
148
232
  - Logout with toast feedback
149
233
 
150
234
  **Computed Signals:**
@@ -153,6 +237,12 @@ User profile dropdown in topbar.
153
237
  | `userName` | `authState.loginUserData()?.name` | `'Guest'` |
154
238
  | `userEmail` | `authState.loginUserData()?.email` | `''` |
155
239
  | `profilePicture` | `layoutService.userProfilePictureUrl()` | `''` |
240
+ | `signUpEnabled` | `APP_CONFIG` via `isSignUpEnabled()` helper | `false` |
241
+
242
+ **Sign-up Link Visibility:**
243
+ - Only shown when `APP_CONFIG.services.auth.features.signUp === true`
244
+ - Controlled via `signUpEnabled()` computed signal
245
+ - Uses `isSignUpEnabled()` helper from `@flusys/ng-core`
156
246
 
157
247
  ### AppCompanyBranchSelector
158
248
 
@@ -207,7 +297,7 @@ Theme customizer dropdown.
207
297
  | Primary colors | `emerald`, `green`, `lime`, `orange`, `amber`, `yellow`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`, `noir` |
208
298
  | Surface colors | `slate`, `gray`, `zinc`, `neutral`, `stone`, `soho`, `viva`, `ocean` |
209
299
  | Presets | `Aura` (default), `Lara`, `Nora` |
210
- | Menu mode | `static`, `overlay` |
300
+ | Menu mode | `static`, `overlay`, `topbar` |
211
301
 
212
302
  Uses `$t()`, `updatePreset()`, `updateSurfacePalette()` from `@primeuix/themes`.
213
303
 
@@ -241,7 +331,7 @@ interface LayoutConfig {
241
331
  primary?: string; // "emerald" | "green" | etc.
242
332
  surface?: string | null; // "slate" | "gray" | etc.
243
333
  darkTheme?: boolean;
244
- menuMode?: 'static' | 'overlay';
334
+ menuMode?: 'static' | 'overlay' | 'topbar';
245
335
  }
246
336
 
247
337
  interface LayoutState {
@@ -249,6 +339,7 @@ interface LayoutState {
249
339
  overlayMenuActive?: boolean;
250
340
  staticMenuMobileActive?: boolean;
251
341
  menuHoverActive?: boolean;
342
+ topbarMenuVisible?: boolean; // For topbar mode on desktop
252
343
  }
253
344
 
254
345
  interface UserProfile { id: string; name: string; email: string; profilePictureUrl?: string | null; }
@@ -281,7 +372,9 @@ interface CompanyProfile { id: string; name: string; slug: string; logoUrl?: str
281
372
  | `hasApps` | Whether apps exist |
282
373
  | `isDarkTheme` | Dark theme active |
283
374
  | `isSidebarActive` | Sidebar visible |
375
+ | `isStatic` | Static menu mode |
284
376
  | `isOverlay` | Overlay menu mode |
377
+ | `isTopbar` | Topbar menu mode (horizontal nav on desktop) |
285
378
  | `getPrimary` / `getSurface` | Current colors |
286
379
  | `userName` / `userEmail` | User info (with fallbacks) |
287
380
  | `userProfilePictureUrl` / `companyLogoUrl` | Image URLs |
@@ -337,7 +430,7 @@ Persists layout configuration to localStorage.
337
430
 
338
431
  **Validation:**
339
432
  - Invalid preset → `'Aura'`
340
- - Invalid menuMode → `'static'`
433
+ - Invalid menuMode → `'static'` (valid: `'static'`, `'overlay'`, `'topbar'`)
341
434
  - Non-boolean darkTheme → `false`
342
435
  - Version mismatch or invalid JSON → Clear and return null
343
436
 
@@ -480,21 +573,51 @@ export class AppComponent {
480
573
 
481
574
  ### Menu Configuration
482
575
 
576
+ **Centralized Config File (v4.0.1):**
577
+
483
578
  ```typescript
484
- const menuItems: IMenuItem[] = [
485
- { id: 'dashboard', label: 'Dashboard', icon: 'pi pi-home', routerLink: ['/dashboard'] },
579
+ // app-menu.config.ts
580
+ import { IMenuItem } from '@flusys/ng-layout';
581
+
582
+ export const APP_MENU: IMenuItem[] = [
486
583
  {
487
- id: 'admin', label: 'Admin', icon: 'pi pi-cog',
584
+ id: 'dashboard',
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,
488
595
  permissionLogic: { id: 'admin', type: 'action', actionId: 'admin.access' },
489
596
  children: [
490
- { id: 'users', label: 'Users', icon: 'pi pi-users', routerLink: ['/admin/users'] },
491
- { id: 'roles', label: 'Roles', icon: 'pi pi-shield', routerLink: ['/admin/roles'] },
597
+ {
598
+ id: 'users',
599
+ labelKey: 'menu.users',
600
+ icon: 'pi pi-users',
601
+ iconType: 1,
602
+ routerLink: ['/admin/users'],
603
+ },
492
604
  ],
493
605
  },
494
- { separator: true },
495
- { id: 'settings', label: 'Settings', icon: 'pi pi-cog', routerLink: ['/settings'] },
496
606
  ];
497
- layoutService.setMenu(menuItems);
607
+ ```
608
+
609
+ **Benefits:**
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:**
616
+ ```typescript
617
+ import { APP_MENU } from './config/app-menu.config.ts';
618
+ import { LayoutService } from '@flusys/ng-layout';
619
+
620
+ layoutService.setMenu(APP_MENU);
498
621
  ```
499
622
 
500
623
  ### Profile Setup
@@ -5,4 +5,11 @@
5
5
  padding: 1rem 0 1rem 0;
6
6
  gap: 0.5rem;
7
7
  border-top: 1px solid var(--surface-border);
8
+
9
+ .logo-image {
10
+ height: 2rem;
11
+ width: auto;
12
+ max-width: 2.5rem;
13
+ object-fit: contain;
14
+ }
8
15
  }
@@ -57,6 +57,24 @@
57
57
  }
58
58
  }
59
59
 
60
+ &.layout-topbar-mode {
61
+ .layout-sidebar {
62
+ display: none;
63
+ }
64
+
65
+ .layout-main-container {
66
+ margin-left: 0;
67
+ padding-left: 2rem;
68
+ padding-top: 6rem;
69
+ }
70
+
71
+ &.layout-topbar-nav-hidden {
72
+ .layout-main-container {
73
+ padding-top: 4rem;
74
+ }
75
+ }
76
+ }
77
+
60
78
  .layout-mask {
61
79
  display: none;
62
80
  }
@@ -1,161 +1,192 @@
1
- @use 'mixins' as *;
1
+ @use "mixins" as *;
2
2
 
3
3
  .layout-topbar {
4
- position: fixed;
5
- height: 4rem;
6
- z-index: 997;
7
- left: 0;
8
- top: 0;
9
- width: 100%;
10
- padding: 0 2rem;
11
- background-color: var(--surface-card);
12
- transition: left var(--layout-section-transition-duration);
4
+ position: fixed;
5
+ height: 4rem;
6
+ z-index: 997;
7
+ left: 0;
8
+ top: 0;
9
+ width: 100%;
10
+ padding: 0 2rem;
11
+ background-color: var(--surface-card);
12
+ transition: left var(--layout-section-transition-duration);
13
+ display: flex;
14
+ align-items: center;
15
+ gap: 1rem;
16
+
17
+ .layout-topbar-logo-container {
18
+ width: 20rem;
13
19
  display: flex;
14
20
  align-items: center;
21
+ flex-shrink: 0;
15
22
 
16
- .layout-topbar-logo-container {
17
- width: 20rem;
18
- display: flex;
19
- align-items: center;
23
+ &.layout-topbar-logo-container-desktop {
24
+ justify-content: space-between;
20
25
  }
26
+ }
21
27
 
22
- .layout-topbar-logo {
23
- display: inline-flex;
24
- align-items: center;
25
- font-size: 1.5rem;
26
- border-radius: var(--content-border-radius);
27
- color: var(--text-color);
28
- font-weight: 500;
29
- gap: 0.5rem;
30
-
31
- svg {
32
- width: 3rem;
33
- }
34
-
35
- &:focus-visible {
36
- @include focused();
37
- }
28
+ .layout-topbar-logo {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ font-size: 1.5rem;
32
+ border-radius: var(--content-border-radius);
33
+ color: var(--text-color);
34
+ font-weight: 500;
35
+ gap: 0.5rem;
36
+
37
+ svg {
38
+ width: 3rem;
38
39
  }
39
40
 
40
- .layout-topbar-action {
41
- display: inline-flex;
42
- justify-content: center;
43
- align-items: center;
44
- color: var(--text-color-secondary);
45
- border-radius: 50%;
46
- width: 2.5rem;
47
- height: 2.5rem;
48
- color: var(--text-color);
49
- transition: background-color var(--element-transition-duration);
50
- cursor: pointer;
51
-
52
- &:hover {
53
- background-color: var(--surface-hover);
54
- }
55
-
56
- &:focus-visible {
57
- @include focused();
58
- }
59
-
60
- i {
61
- font-size: 1.25rem;
62
- }
63
-
64
- span {
65
- font-size: 1rem;
66
- display: none;
67
- }
41
+ .logo-image {
42
+ height: 2.5rem;
43
+ width: auto;
44
+ max-width: 3rem;
45
+ object-fit: contain;
46
+ }
68
47
 
69
- &.layout-topbar-action-highlight {
70
- background-color: var(--primary-color);
71
- color: var(--primary-contrast-color);
72
- }
48
+ &:focus-visible {
49
+ @include focused();
73
50
  }
51
+ }
74
52
 
75
- .layout-menu-button {
76
- margin-right: 0.5rem;
53
+ .layout-topbar-action {
54
+ display: inline-flex;
55
+ justify-content: center;
56
+ align-items: center;
57
+ color: var(--text-color-secondary);
58
+ border-radius: 50%;
59
+ width: 2.5rem;
60
+ height: 2.5rem;
61
+ color: var(--text-color);
62
+ transition: background-color var(--element-transition-duration);
63
+ cursor: pointer;
64
+ flex-shrink: 0;
65
+
66
+ &:hover {
67
+ background-color: var(--surface-hover);
77
68
  }
78
69
 
79
- .layout-topbar-menu-button {
80
- display: none;
70
+ &:focus-visible {
71
+ @include focused();
81
72
  }
82
73
 
83
- .layout-topbar-actions {
84
- margin-left: auto;
85
- display: flex;
86
- gap: 1rem;
74
+ i {
75
+ font-size: 1.25rem;
87
76
  }
88
77
 
89
- .layout-topbar-menu-content {
90
- display: flex;
91
- gap: 1rem;
78
+ span {
79
+ font-size: 1rem;
80
+ display: none;
92
81
  }
93
82
 
94
- .layout-config-menu {
95
- display: flex;
96
- gap: 1rem;
83
+ &.layout-topbar-action-highlight {
84
+ background-color: var(--primary-color);
85
+ color: var(--primary-contrast-color);
86
+ }
87
+ }
88
+
89
+ .layout-topbar-action-right {
90
+ margin-left: auto !important;
91
+ margin-right: -0.9rem !important;
92
+ }
93
+
94
+ .layout-menu-button {
95
+ margin-right: 0.5rem;
96
+ }
97
+
98
+ .layout-menu-button-right {
99
+ order: -1;
100
+ margin-right: 1rem;
101
+ opacity: 1;
102
+ transition: opacity var(--element-transition-duration);
103
+
104
+ &.hidden {
105
+ display: none;
106
+ opacity: 0;
97
107
  }
108
+ }
109
+
110
+ .layout-topbar-menu-button {
111
+ display: none;
112
+ }
113
+
114
+ .layout-topbar-actions {
115
+ margin-left: auto;
116
+ display: flex;
117
+ gap: 1rem;
118
+ flex-shrink: 0;
119
+ }
120
+
121
+ .layout-topbar-menu-content {
122
+ display: flex;
123
+ gap: 1rem;
124
+ }
125
+
126
+ .layout-config-menu {
127
+ display: flex;
128
+ gap: 1rem;
129
+ }
98
130
  }
99
131
 
100
132
  @media (max-width: 991px) {
101
- .layout-topbar {
102
- padding: 0 2rem;
133
+ .layout-topbar {
134
+ padding: 0 2rem;
103
135
 
104
- .layout-topbar-logo-container {
105
- width: auto;
106
- }
136
+ .layout-topbar-logo-container {
137
+ width: auto;
138
+ }
107
139
 
108
- .layout-menu-button {
109
- margin-left: 0;
110
- margin-right: 0.5rem;
111
- }
140
+ .layout-menu-button {
141
+ margin-left: 0;
142
+ margin-right: 0.5rem;
143
+ }
112
144
 
113
- .layout-topbar-menu-button {
114
- display: inline-flex;
115
- }
145
+ .layout-topbar-menu-button {
146
+ display: inline-flex;
147
+ }
148
+
149
+ .layout-topbar-menu {
150
+ position: absolute;
151
+ background-color: var(--surface-overlay);
152
+ transform-origin: top;
153
+ box-shadow:
154
+ 0px 3px 5px rgba(0, 0, 0, 0.02),
155
+ 0px 0px 2px rgba(0, 0, 0, 0.05),
156
+ 0px 1px 4px rgba(0, 0, 0, 0.08);
157
+ border-radius: var(--content-border-radius);
158
+ padding: 1rem;
159
+ right: 2rem;
160
+ top: 4rem;
161
+ min-width: 15rem;
162
+ border: 1px solid var(--surface-border);
163
+
164
+ .layout-topbar-menu-content {
165
+ gap: 0.5rem;
166
+ }
167
+
168
+ .layout-topbar-action {
169
+ display: flex;
170
+ width: 100%;
171
+ height: auto;
172
+ justify-content: flex-start;
173
+ border-radius: var(--content-border-radius);
174
+ padding: 0.5rem 1rem;
116
175
 
117
- .layout-topbar-menu {
118
- position: absolute;
119
- background-color: var(--surface-overlay);
120
- transform-origin: top;
121
- box-shadow:
122
- 0px 3px 5px rgba(0, 0, 0, 0.02),
123
- 0px 0px 2px rgba(0, 0, 0, 0.05),
124
- 0px 1px 4px rgba(0, 0, 0, 0.08);
125
- border-radius: var(--content-border-radius);
126
- padding: 1rem;
127
- right: 2rem;
128
- top: 4rem;
129
- min-width: 15rem;
130
- border: 1px solid var(--surface-border);
131
-
132
- .layout-topbar-menu-content {
133
- gap: 0.5rem;
134
- }
135
-
136
- .layout-topbar-action {
137
- display: flex;
138
- width: 100%;
139
- height: auto;
140
- justify-content: flex-start;
141
- border-radius: var(--content-border-radius);
142
- padding: 0.5rem 1rem;
143
-
144
- i {
145
- font-size: 1rem;
146
- margin-right: 0.5rem;
147
- }
148
-
149
- span {
150
- font-weight: medium;
151
- display: block;
152
- }
153
- }
176
+ i {
177
+ font-size: 1rem;
178
+ margin-right: 0.5rem;
154
179
  }
155
180
 
156
- .layout-topbar-menu-content {
157
- flex-direction: column;
181
+ span {
182
+ font-weight: medium;
183
+ display: block;
158
184
  }
185
+ }
159
186
  }
160
- }
161
187
 
188
+ .layout-topbar-menu-content {
189
+ flex-direction: column;
190
+ }
191
+ }
192
+ }