@lukfel/ng-scaffold 22.1.14 → 22.2.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 CHANGED
@@ -1,644 +1,646 @@
1
- # Documentation
2
- ## Introduction
3
-
4
- This Angular library provides a foundational scaffold for modern web and mobile applications. It includes essential UI elements such as a header, sidebar, drawer, footer, floating button, and built-in services for theme switching, language switching, snackbar notifications, dialog management, and breakpoint detection. Simply wrap your `router-outlet` with the `lf-scaffold` element and configure the `ScaffoldConfig` within the `ScaffoldService`.
5
-
6
- - **NPM**: [@lukfel/ng-scaffold](https://www.npmjs.com/package/@lukfel/ng-scaffold)
7
- - **Demo**: [lukfel.github.io/ng-scaffold](https://lukfel.github.io/ng-scaffold)
8
- - **Examples**: [Uglygotchi](https://www.uglygotchi.at), [What a Waste](https://www.what-a-waste.at), [Create a Tournament](https://www.create-a-tournament.com), [Wowen](https://www.wowen.at)
9
-
10
-
11
-
12
-
13
- ## Installation
14
- Install the package using `npm install` or `ng add` (with experimental Angular schematics):
15
-
16
- ```sh
17
- npm install @lukfel/ng-scaffold
18
- ```
19
-
20
- ```sh
21
- ng add @lukfel/ng-scaffold
22
- ```
23
-
24
- The ng add command will additionally try to perform the following actions:
25
- * Import `ScaffoldComponent` in root
26
- * Inject `ScaffoldService` and initialize `ScaffoldConfig`
27
- * Wrap template with `<lf-scaffold>`
28
- * Include styles
29
-
30
-
31
- ## Component
32
- Import the `ScaffoldComponent` into your `app.module.ts` or `app.component.ts` (standalone).
33
-
34
- ```ts
35
- import { ScaffoldComponent } from '@lukfel/ng-scaffold';
36
-
37
- ...
38
- imports: [
39
- ScaffoldComponent
40
- ]
41
- ```
42
-
43
- ### (Optional) Provider
44
- The library uses the built-in logging service `Logger` to log debugging events when a `ScaffoldLibraryConfig` is provided and `debugging` is set to `true`. Logging is automatically disabled in production mode when `production` is set to `true`.
45
-
46
- ```ts
47
- import { provideScaffold } from '@lukfel/ng-scaffold';
48
- import { isDevMode } from '@angular/core';
49
-
50
- ...
51
- providers: [
52
- provideScaffold({
53
- production: !isDevMode(),
54
- debugging: isDevMode()
55
- })
56
- ]
57
- ```
58
-
59
-
60
-
61
-
62
- ## Configuration
63
- Import the `ScaffoldService` in `app.component.ts` to manage the `ScaffoldConfig`.
64
-
65
- ```ts
66
- import { ScaffoldService } from '@lukfel/ng-scaffold';
67
-
68
- export class AppComponent {
69
-
70
- private scaffoldService = inject(ScaffoldService);
71
-
72
- private scaffoldConfig: ScaffoldConfig = {
73
- // Create your own config or generate it at https://lukfel.github.io/ng-scaffold
74
- headerConfig: { enable: true, title: 'Scaffold works!' }
75
- };
76
-
77
- constructor() {
78
- this.scaffoldService.scaffoldConfig = this.scaffoldConfig;
79
- }
80
- }
81
- ```
82
-
83
- ### (Optional) Update Configuration
84
- The `ScaffoldService` provides `updateScaffoldProperty()` to partially update the `ScaffoldConfig` in a type-safe way. It performs an immutable update, creating a new configuration object with the updated property and emits the new state.
85
-
86
- ```ts
87
- import { ScaffoldService, DrawerConfig, HeaderConfig, MenuButton } from '@lukfel/ng-scaffold';
88
-
89
- export class AppComponent {
90
-
91
- ...
92
-
93
- // Example #1: Toggle the drawer open state
94
- public toggleDrawer(): void {
95
- const open: boolean = this.scaffoldService?.scaffoldConfig?.drawerConfig?.open || false;
96
- this.scaffoldService.updateScaffoldProperty('drawerConfig', { open: !open });
97
- }
98
-
99
- // Example #2: Enable the header input field
100
- public enableHeaderInput(): void {
101
- this.scaffoldService.updateScaffoldProperty('headerConfig', inputConfig: { enable: true });
102
- }
103
-
104
- // Example #2: Add new button to navbar
105
- public addNavbarButton(button: MenuButton): void {
106
- const currentNavbarConfig: NavbarConfig = this.scaffoldService.scaffoldConfig.navbarConfig;
107
- this.scaffoldService.updateScaffoldProperty('navbarConfig', { menuButtons: [...currentNavbarConfig.menuButtons, button] });
108
- }
109
- }
110
- ```
111
-
112
-
113
-
114
-
115
- ## Template
116
- Wrap your application’s content inside the `lf-scaffold` component in `app.component.html`.
117
-
118
- ```html
119
- <lf-scaffold>
120
- <ng-container drawerContent></ng-container> <!-- (Optional) content projection for drawer -->
121
- <router-outlet></router-outlet>
122
- </lf-scaffold>
123
- ```
124
-
125
-
126
-
127
-
128
- ## Styling
129
- Import the styles in your `styles.scss` and apply the default theme.
130
-
131
- * **Note:** The library’s styles include Material icons and Roboto font styles.
132
-
133
- ```scss
134
- @use "@lukfel/ng-scaffold/styles" as lf;
135
- @include lf.scaffold-theme(); // Include default theme
136
- ```
137
-
138
- ### (Optional) Custom Themes
139
- To customize the default theme, define a new theme map specifying `primary`, `accent`, and `warn` colors using Material palettes. Enabling the `dark` option applies a dark theme. Pass your custom theme to `lf.scaffold-theme($my-theme)`.
140
-
141
- ```scss
142
- @use "@lukfel/ng-scaffold/styles" as lf;
143
- @use '@angular/material' as mat;
144
-
145
- // Define theme (use Material palettes or create your own)
146
- $base-theme: (
147
- primary: mat.m2-define-palette(mat.$m2-pink-palette),
148
- accent: mat.m2-define-palette(mat.$m2-blue-palette),
149
- warn: mat.m2-define-palette(mat.$m2-red-palette),
150
- dark: false
151
- );
152
-
153
- @include lf.scaffold-theme($base-theme);
154
- ```
155
-
156
- ### (Optional) Multiple Themes
157
- To switch between multiple themes dynamically, define additional themes using `lf.scaffold-colors($theme, 'theme-class')`, then apply the class to the `<body class="theme-class">` tag.
158
-
159
- * **Note:** The `ThemeService` allows dynamic theme switching.
160
-
161
- ```scss
162
- @use "@lukfel/ng-scaffold/styles" as lf;
163
- @use '@angular/material' as mat;
164
-
165
- // Define themes (use Material palettes or create your own)
166
- $base-theme: (
167
- primary: mat.m2-define-palette(mat.$m2-pink-palette),
168
- accent: mat.m2-define-palette(mat.$m2-blue-palette),
169
- warn: mat.m2-define-palette(mat.$m2-red-palette),
170
- dark: false
171
- );
172
-
173
- $theme2: (
174
- primary: mat.m2-define-palette(mat.$m2-pink-palette),
175
- accent: mat.m2-define-palette(mat.$m2-blue-palette),
176
- dark: true
177
- );
178
-
179
- // Include themes (use ThemeService to switch between themes)
180
- @include lf.scaffold-theme($base-theme);
181
- @include lf.scaffold-colors($theme2, 'theme2');
182
- ```
183
-
184
- ### (Optional) Custom Typography
185
- To change the default typography from Roboto, pass an additional parameter ``font-family`` in the theme map.
186
-
187
- * **Note:** Don't forget to also import and set the font-family in the styles.
188
-
189
- ```scss
190
- @use "@lukfel/ng-scaffold/styles" as lf;
191
- @use '@angular/material' as mat;
192
-
193
- $base-theme: (
194
- primary: mat.m2-define-palette(mat.$m2-pink-palette),
195
- accent: mat.m2-define-palette(mat.$m2-blue-palette),
196
- warn: mat.m2-define-palette(mat.$m2-red-palette),
197
- dark: false,
198
- font-family: 'Comic Sans'
199
- );
200
-
201
- @include lf.scaffold-theme($base-theme);
202
-
203
- body {
204
- font-family: "Comic Sans MS" !important;
205
- }
206
- ```
207
-
208
-
209
-
210
-
211
- ## Events
212
- There are two ways to listen to scaffold user events (button clicks, input changes, ...):
213
-
214
- ### (Recommended) Option 1 – Subscribe to Event Observables
215
- Subscribe to the event Observables and listen to changes
216
- ```ts
217
- private scaffoldService = inject(ScaffoldService);
218
- private router = inject(Router);
219
-
220
- constructor() {
221
- // Listen to click events (header menu and navbar buttons - click)
222
- this.scaffoldService.buttonClickEventValue$.subscribe((id: string) => {
223
- this.router.navigate([id]);
224
- });
225
-
226
- // Listen to header input change events (header input field - change)
227
- this.scaffoldService.headerInputChangeValue$.subscribe((value: string) => {
228
- ...
229
- });
230
- }
231
- ```
232
-
233
- ### Option 2 – Use Output Events
234
- Specify the needed output events and call custom methods
235
- ```html
236
- <lf-scaffold
237
- (headerButtonClickEvent)="headerButtonClickEvent($event)"
238
- (headerInputSubmitEvent)="headerInputSubmitEvent($event)"
239
- (headerInputChangeEvent)="headerInputChangeEvent($event)"
240
- (navbarButtonClickEvent)="navbarButtonClickEvent($event)">
241
- <router-outlet></router-outlet>
242
- </lf-scaffold>
243
- ```
244
-
245
- ```ts
246
- // Listen to header click events (header menu buttons - click)
247
- public headerButtonClickEvent(id: string): void {
248
- this.router.navigate([id]);
249
- }
250
-
251
- // Listen to header input submit events (header input field - submit)
252
- public headerInputSubmitEvent(value: string): void {
253
- ...
254
- }
255
-
256
- // Listen to header input change events (header input field - change)
257
- public headerInputChangeEvent(value: string): void {
258
- ...
259
- }
260
-
261
- // Listen to navbar click events (navbar menu buttons - click)
262
- public navbarButtonClickEvent(id: string): void {
263
- this.router.navigate([id]);
264
- }
265
- ```
266
-
267
-
268
-
269
-
270
- ## (Optional) Additional Services
271
- This library includes several utility services:
272
-
273
- - **`Logger`** – Development-only logging
274
- - **`SnackbarService`** – Display snackbar notifications
275
- - **`DialogService`** – Display custom dialogs
276
- - **`BreakpointService`** – Detect screen breakpoints
277
- - **`ThemeService`** – Manage themes dynamically
278
- - **`RouterService`** – Track route changes and retrieve route history
279
- - **`SeoService`** – Manage meta tags
280
- - **`LocalStorageService`** – Handle local storage
281
- - **`TranslationService`** – Switch languages at runtime with per-locale JSON files
282
-
283
- ### Logger
284
- Logs information during development and hides all logs during production.
285
-
286
- * **Note:** Use the provoder `provideScaffold({ production: !isDevMode() })` to disable logs in production
287
-
288
- ```ts
289
- import { Logger } from '@lukfel/ng-scaffold';
290
-
291
- export class AppComponent {
292
-
293
- private logger = inject(Logger);
294
-
295
- // Generic api call with logging
296
- public apiCallWithLogging(): void {
297
- this.apiService.apiCall().then(result => {
298
- this.logger.log(result);
299
- }).catch(error => {
300
- this.logger.error(error);
301
- });
302
- }
303
- }
304
- ```
305
-
306
- ### SnackbarService
307
- Provides basic methods to display simple snackbar notifications with or without actions.
308
-
309
- ```ts
310
- import { SnackbarService } from '@lukfel/ng-scaffold';
311
-
312
- export class AppComponent {
313
-
314
- private snackbarService = inject(SnackbarService);
315
-
316
- // Generic api call with snackbar response
317
- public apiCallWithSnackbarResponse(): void {
318
- this.apiService.apiCall().then(result => {
319
- this.snackbarService.openSnackbar('Call was successful');
320
- }).catch(error => {
321
- this.snackbarService.openSnackbar('Call was not successful');
322
- });
323
- }
324
- }
325
- ```
326
-
327
- ### DialogService
328
- Includes a basic confirmation dialog thar returns a `Promise`. Use the method `openCustomDialog` to pass your own dialog template and config.
329
-
330
- ```ts
331
- import { DialogService } from '@lukfel/ng-scaffold';
332
-
333
- export class AppComponent {
334
-
335
- private dialogService = inject(DialogService);
336
-
337
- // Generic api call with a subsequent confirmation dialog
338
- public apiCallWithDialogConfirmation(): void {
339
- this.dialogService.openConfirmDialog('Do you really want to make this api call?').then(response => {
340
- // If the user confirmed the dialog, go through with the api call
341
- if(response === true) {
342
- this.apiService.apiCall().then(result => {
343
- ...
344
- }).catch(error => {
345
- ...
346
- });
347
- }
348
- });
349
- }
350
- }
351
- ```
352
-
353
- ### BreakpointService
354
- Allows you to subscribe to breakpoint changes.
355
-
356
- ```ts
357
- import { BreakpointService } from '@lukfel/ng-scaffold';
358
-
359
- export class AppComponent {
360
-
361
- private breakpointService = inject(BreakpointService);
362
-
363
- constructor() {
364
- this.breakpointService.breakpoint$.subscribe((result: BreakpointState) => {
365
- // Check which breakpoint is active
366
- if (result.breakpoints[Breakpoints.XSmall]) {
367
- ...
368
- } else if (result.breakpoints[Breakpoints.Small]) {
369
- ...
370
- } else if (result.breakpoints[Breakpoints.Medium]) {
371
- ...
372
- } else if (result.breakpoints[Breakpoints.Large]) {
373
- ...
374
- }
375
- });
376
- }
377
- }
378
- ```
379
-
380
- ### ThemeService
381
- Dynamically change between your defined themes.
382
-
383
- * **Note:** The theme must be defined and included in your styles
384
-
385
- ```ts
386
- import { ThemeService } from '@lukfel/ng-scaffold';
387
-
388
- export class AppComponent {
389
-
390
- private themeService = inject(ThemeService);
391
-
392
- constructor(private themeService: ThemeService) {
393
- this.themeService.setTheme('my-theme2', true); // set theme and store in local storage (using the built in LocalStorageService)
394
- }
395
- }
396
- ```
397
-
398
- ### TranslationService
399
- Switches the application language at runtime by loading a separate JSON file per locale (e.g. `de.json`, `en.json`) that share the same keys. Keys missing from a locale are automatically resolved from the `fallbackLocale`.
400
-
401
- * **Note:** Configure the available locales in the `provideScaffold` provider and serve the `<locale>.json` files from the configured `path` (default `assets/i18n/`)
402
-
403
- ```ts
404
- import { provideScaffold } from '@lukfel/ng-scaffold';
405
-
406
- ...
407
- providers: [
408
- provideScaffold({
409
- language: {
410
- locales: ['de', 'en'], // available locales (also drives a language picker)
411
- fallbackLocale: 'en', // used when a locale is unknown or a key is missing
412
- path: 'assets/i18n/', // (Optional) base path of the <locale>.json files
413
- persist: true // (Optional) store the active locale in local storage
414
- }
415
- })
416
- ]
417
- ```
418
-
419
- Inject the `TranslationService` to read the translations and switch the locale. Each app keeps its own strongly-typed `TransMap` interface and reads the labels through the typed accessors.
420
-
421
- ```ts
422
- import { TranslationService } from '@lukfel/ng-scaffold';
423
-
424
- export class AppComponent {
425
-
426
- private translationService = inject(TranslationService);
427
-
428
- // Typed signal of the active translations (empty until the first load)
429
- public transMap = this.translationService.transMapAs<TransMap>();
430
-
431
- // Active locale and all configured locales (e.g. for a language picker)
432
- public locale = this.translationService.locale;
433
- public locales = this.translationService.locales;
434
-
435
- // Switch the active language (persisted according to the config)
436
- public changeLanguage(locale: string): void {
437
- this.translationService.setLocale(locale);
438
- }
439
- }
440
- ```
441
-
442
- * **Note:** For observable consumers use `locale$` and `transMap$` (or the typed `transMapStream<TransMap>()`, which only emits once translations are loaded). Track the loading state with the `loading` signal or `loading$`.
443
-
444
-
445
-
446
-
447
- ## (Optional) Standalone Components
448
- In addition to the scaffold components provided by default by the the `ScaffoldComponent` there are several standalone components that can be utilized.
449
-
450
- * **Note:** Standalone components must be imported manually and are not part of the `ScaffoldComponent` import
451
-
452
- ### List
453
- A standalone Material Design inspired list and table hybrid component for displaying structured
454
- collections of items. It supports avatars, titles, subtitles, actions, selections and dragging —
455
- making it ideal for dashboards, inventories, and administrative views.
456
-
457
- ```ts
458
- import { ListComponent } from '@lukfel/ng-scaffold';
459
- ```
460
-
461
- ```ts
462
- import { Button, ListConfig, ListHeader, ListItem } from '@lukfel/ng-scaffold';
463
-
464
- public listConfig = signal<ListConfig>({ // (Optional) list config
465
- enableSelection: true,
466
- enableDragging: true,
467
- initialSortToken: 'title',
468
- initialSortAsc: true,
469
- showDividers: true
470
- });
471
-
472
- public listHeader = signal<ListHeader>({ // (Optional) list header
473
- matIcon: 'sort',
474
- items: [
475
- { title: 'Items', sortToken: 'title' }
476
- ]
477
- });
478
-
479
- public listItems = signal<ListItem[]>([
480
- { id: 1, svgIcon: 'logo', title: 'Item 2', subtitle: 'I am disabled', disabled: true },
481
- { id: 0, avatar: 'img/logos/ic_launcher-web.png', title: 'Item 1', subtitle: 'I am clickable', clickable: true },
482
- { id: 2, matIcon: 'person', title: 'Item 3', subtitle: 'I have no edit buton', hiddenButtonIds: ['edit'] },
483
- ]);
484
-
485
- public listButtons = signal<Button[]>([ // (Optional) list buttons
486
- { id: 'edit', matIcon: 'edit' },
487
- { id: 'delete', matIcon: 'delete', cssClass: 'warn' }
488
- ]);
489
-
490
- // (Optional) Handle sort events
491
- public onListSortChange(event: { sortToken: string, sortAsc: boolean }): void {
492
- if (event?.sortToken === 'title') {
493
- this.listItems().sort((a, b) => {
494
- if (!a.title || !b.title) return 0;
495
- if (event.sortAsc) return a.title.localeCompare(b.title);
496
- return b.title.localeCompare(a.title);
497
- });
498
- }
499
- }
500
-
501
- public onListSelectionChange(items: ListItem[]): void {
502
- // handle selection change
503
- }
504
-
505
- public onListButtonClick(event: { buttonId: string, item: ListItem }): void {
506
- // handle button click
507
- }
508
-
509
- public onListItemClick(item: ListItem): void {
510
- // handle item click
511
- }
512
- ```
513
-
514
- ```html
515
- <lf-list
516
- [config]="listConfig()"
517
- [header]="listHeader()"
518
- [items]="listItems()"
519
- [buttons]="listButtons()"
520
- (sortChangeEvent)="onListSortChange($event)"
521
- (selectionChangeEvent)="onListSelectionChange()"
522
- (buttonClickEvent)="onListButtonClick($event)"
523
- (itemClickEvent)="onListItemClick($event)"></lf-list>
524
- ```
525
-
526
- ### File-Upload
527
- A standalone Material Design styled button for selecting and uploading files. It wraps a hidden
528
- native file input and exposes a simple, customizable interface with built-in icon, tooltip, and
529
- state management.
530
-
531
- ```ts
532
- import { FileUploadComponent } from '@lukfel/ng-scaffold';
533
- ```
534
-
535
- ```ts
536
- public onFileChange(file: File): void {
537
- // handle file upload
538
- }
539
- ```
540
-
541
- ```html
542
- <lf-file-upload
543
- color="accent"
544
- label="Upload File"
545
- matIcon="upload"
546
- accept="*"
547
- (fileChangeEvent)="onFileChange($event)"></lf-file-upload>
548
- ```
549
-
550
- ### Color-Picker
551
- A standalone Material Design styled button for picking colors (uses native design on Safari). It wraps a hidden native color
552
- input and exposes a simple, customizable interface with built-in icon, tooltip, and state
553
- management.
554
-
555
- ```ts
556
- import { ColorPickerComponent } from '@lukfel/ng-scaffold';
557
- ```
558
-
559
- ```ts
560
- public onColorChange(color: string): void {
561
- // handle color pick
562
- }
563
- ```
564
-
565
- ```html
566
- <lf-color-picker
567
- color="accent"
568
- label="Pick Color"
569
- matIcon="color_lens"
570
- (colorChangeEvent)="onColorChange($event)"></lf-color-picker>
571
- ```
572
-
573
- ### Placeholder
574
- A standalone UI component designed to display an informative placeholder or empty state when no
575
- data is available. It provides a structured layout for an icon, heading, message, and optional
576
- action — helping guide users toward the next step.
577
-
578
- ```ts
579
- import { PlaceholderComponent } from '@lukfel/ng-scaffold';
580
- ```
581
-
582
- ```ts
583
- import { PlaceholderConfig } from '@lukfel/ng-scaffold';
584
-
585
- public placeholderConfig = signal<PlaceholderConfig>({
586
- matIcon: 'widgets',
587
- title: 'Title',
588
- message: 'This is a placeholder message.',
589
- button: {
590
- id: 'placeholder',
591
- label: 'ACTION'
592
- }
593
- });
594
-
595
- public onPlaceholderButtonClick(): void {
596
- // handle placeholder click
597
- }
598
- ```
599
-
600
- ```html
601
- <lf-placeholder
602
- [placeholderConfig]="placeholderConfig()"
603
- (buttonClickEvent)="onPlaceholderButtonClick()"></lf-placeholder>
604
- ```
605
-
606
- ### Notification
607
- A standalone standalone Material Design inspired notification component for displaying transient
608
- messages to users. It supports different severity levels (primary, accent, warn), optional
609
- icons, optional actions, and links.
610
-
611
- ```ts
612
- import { NotificationComponent } from '@lukfel/ng-scaffold';
613
- ```
614
-
615
- ```html
616
- <lf-notification
617
- [static]="true"
618
- color="accent"
619
- message="This is an info notification with action."
620
- matIcon="info"
621
- action="Action"
622
- (clickEvent)="onNotificationClick()"></lf-notification>
623
- ```
624
-
625
-
626
-
627
-
628
- ## (Optional) Interceptors
629
- Intercept HTTP Calls and automatically show a loading spinner.
630
-
631
- * **Note:** The loading spinner can also be manually shown by udpating the value for `scaffoldConfig.loading` in the `ScaffoldService`
632
-
633
- ```ts
634
- import { ScaffoldLoadingInterceptor } from '@lukfel/ng-scaffold';
635
-
636
- ...
637
- providers: [
638
- {
639
- provide: HTTP_INTERCEPTORS,
640
- useClass: ScaffoldLoadingInterceptor,
641
- multi: true
642
- }
643
- ]
1
+ # Documentation
2
+ ## Introduction
3
+
4
+ This Angular library provides a foundational scaffold for modern web and mobile applications. It includes essential UI elements such as a header, sidebar, drawer, footer, floating button, and built-in services for theme switching, language switching, snackbar notifications, dialog management, and breakpoint detection. Simply wrap your `router-outlet` with the `lf-scaffold` element and configure the `ScaffoldConfig` within the `ScaffoldService`.
5
+
6
+ - **NPM**: [@lukfel/ng-scaffold](https://www.npmjs.com/package/@lukfel/ng-scaffold)
7
+ - **Demo**: [lukfel.github.io/ng-scaffold](https://lukfel.github.io/ng-scaffold)
8
+ - **Examples**: [Uglygotchi](https://www.uglygotchi.at), [What a Waste](https://www.what-a-waste.at), [Create a Tournament](https://www.create-a-tournament.com), [Wowen](https://www.wowen.at)
9
+
10
+
11
+
12
+
13
+ ## Installation
14
+ Install the package using `npm install` or `ng add` (with experimental Angular schematics):
15
+
16
+ ```sh
17
+ npm install @lukfel/ng-scaffold
18
+ ```
19
+
20
+ ```sh
21
+ ng add @lukfel/ng-scaffold
22
+ ```
23
+
24
+ The ng add command will additionally try to perform the following actions:
25
+ * Import `ScaffoldComponent` in root
26
+ * Inject `ScaffoldService` and initialize `ScaffoldConfig`
27
+ * Wrap template with `<lf-scaffold>`
28
+ * Include styles
29
+
30
+
31
+ ## Component
32
+ Import the `ScaffoldComponent` into your `app.module.ts` or `app.component.ts` (standalone).
33
+
34
+ ```ts
35
+ import { ScaffoldComponent } from '@lukfel/ng-scaffold';
36
+
37
+ ...
38
+ imports: [
39
+ ScaffoldComponent
40
+ ]
41
+ ```
42
+
43
+ ### (Optional) Provider
44
+ The library uses the built-in logging service `Logger` to log debugging events when a `ScaffoldLibraryConfig` is provided and `debugging` is set to `true`. Logging is automatically disabled in production mode when `production` is set to `true`.
45
+
46
+ ```ts
47
+ import { provideScaffold } from '@lukfel/ng-scaffold';
48
+ import { isDevMode } from '@angular/core';
49
+
50
+ ...
51
+ providers: [
52
+ provideScaffold({
53
+ production: !isDevMode(),
54
+ debugging: isDevMode()
55
+ })
56
+ ]
57
+ ```
58
+
59
+
60
+
61
+
62
+ ## Configuration
63
+ Import the `ScaffoldService` in `app.component.ts` to manage the `ScaffoldConfig`.
64
+
65
+ ```ts
66
+ import { ScaffoldService } from '@lukfel/ng-scaffold';
67
+
68
+ export class AppComponent {
69
+
70
+ private scaffoldService = inject(ScaffoldService);
71
+
72
+ private scaffoldConfig: ScaffoldConfig = {
73
+ // Create your own config or generate it at https://lukfel.github.io/ng-scaffold
74
+ headerConfig: { enable: true, title: 'Scaffold works!' }
75
+ };
76
+
77
+ constructor() {
78
+ this.scaffoldService.scaffoldConfig = this.scaffoldConfig;
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### (Optional) Update Configuration
84
+ The `ScaffoldService` provides `updateScaffoldProperty()` to partially update the `ScaffoldConfig` in a type-safe way. It performs an immutable update, creating a new configuration object with the updated property and emits the new state.
85
+
86
+ ```ts
87
+ import { ScaffoldService, DrawerConfig, HeaderConfig, MenuButton } from '@lukfel/ng-scaffold';
88
+
89
+ export class AppComponent {
90
+
91
+ ...
92
+
93
+ // Example #1: Toggle the drawer open state
94
+ public toggleDrawer(): void {
95
+ const open: boolean = this.scaffoldService?.scaffoldConfig?.drawerConfig?.open || false;
96
+ this.scaffoldService.updateScaffoldProperty('drawerConfig', { open: !open });
97
+ }
98
+
99
+ // Example #2: Enable the header input field
100
+ public enableHeaderInput(): void {
101
+ this.scaffoldService.updateScaffoldProperty('headerConfig', inputConfig: { enable: true });
102
+ }
103
+
104
+ // Example #2: Add new button to navbar
105
+ public addNavbarButton(button: MenuButton): void {
106
+ const currentNavbarConfig: NavbarConfig = this.scaffoldService.scaffoldConfig.navbarConfig;
107
+ this.scaffoldService.updateScaffoldProperty('navbarConfig', { menuButtons: [...currentNavbarConfig.menuButtons, button] });
108
+ }
109
+ }
110
+ ```
111
+
112
+
113
+
114
+
115
+ ## Template
116
+ Wrap your application’s content inside the `lf-scaffold` component in `app.component.html`.
117
+
118
+ ```html
119
+ <lf-scaffold>
120
+ <ng-container drawerContent></ng-container> <!-- (Optional) content projection for drawer -->
121
+ <router-outlet></router-outlet>
122
+ </lf-scaffold>
123
+ ```
124
+
125
+
126
+
127
+
128
+ ## Styling
129
+ Import the styles in your `styles.scss` and apply the default theme.
130
+
131
+ * **Note:** The library’s styles include Material icons and Roboto font styles.
132
+
133
+ ```scss
134
+ @use "@lukfel/ng-scaffold/styles" as lf;
135
+ @include lf.scaffold-theme-m3(); // Include default theme
136
+ ```
137
+
138
+ * **Note:** `ng add` sets this up for you and can generate the Material 2 variant instead.
139
+
140
+ ### (Optional) Custom Themes
141
+ To customize the default theme, define a new theme map specifying `primary` and `tertiary` colors using Material palettes. `theme-type` takes `light`, `dark` or `color-scheme` (follows the OS setting). Pass your custom theme to `lf.scaffold-theme-m3($my-theme)`.
142
+
143
+ ```scss
144
+ @use "@lukfel/ng-scaffold/styles" as lf;
145
+ @use '@angular/material' as mat;
146
+
147
+ // Define theme (use Material palettes or create your own)
148
+ $base-theme: (
149
+ primary: mat.$rose-palette,
150
+ tertiary: mat.$blue-palette,
151
+ theme-type: light
152
+ );
153
+
154
+ @include lf.scaffold-theme-m3($base-theme);
155
+ ```
156
+
157
+ ### (Optional) Multiple Themes
158
+ To switch between multiple themes dynamically, define additional themes using `lf.scaffold-colors-m3($theme, 'theme-class')`, then apply the class to the `<body class="theme-class">` tag.
159
+
160
+ * **Note:** The `ThemeService` allows dynamic theme switching.
161
+
162
+ ```scss
163
+ @use "@lukfel/ng-scaffold/styles" as lf;
164
+ @use '@angular/material' as mat;
165
+
166
+ // Define themes (use Material palettes or create your own)
167
+ $base-theme: (
168
+ primary: mat.$rose-palette,
169
+ tertiary: mat.$blue-palette,
170
+ theme-type: light
171
+ );
172
+
173
+ $theme2: (
174
+ primary: mat.$rose-palette,
175
+ tertiary: mat.$blue-palette,
176
+ theme-type: dark
177
+ );
178
+
179
+ // Include themes (use ThemeService to switch between themes)
180
+ @include lf.scaffold-theme-m3($base-theme);
181
+ @include lf.scaffold-colors-m3($theme2, 'theme2');
182
+ ```
183
+
184
+ ### (Optional) Custom Typography
185
+ To change the default typography from Roboto, pass an additional parameter ``font-family`` in the theme map.
186
+
187
+ * **Note:** Don't forget to also import and set the font-family in the styles.
188
+
189
+ ```scss
190
+ @use "@lukfel/ng-scaffold/styles" as lf;
191
+ @use '@angular/material' as mat;
192
+
193
+ $base-theme: (
194
+ primary: mat.$rose-palette,
195
+ tertiary: mat.$blue-palette,
196
+ theme-type: light,
197
+ font-family: 'Comic Sans'
198
+ );
199
+
200
+ @include lf.scaffold-theme-m3($base-theme);
201
+
202
+ body {
203
+ font-family: "Comic Sans MS" !important;
204
+ }
205
+ ```
206
+
207
+ ### (Optional) Material 2 Theming
208
+ Material 2 is still supported through `lf.scaffold-theme-m2($theme)` and `lf.scaffold-colors-m2($theme, 'theme-class')`. Its theme map uses `primary`, `accent`, `warn` and `dark`, with palettes from `mat.m2-define-palette()`. Use one API or the other, not both.
209
+
210
+ * **Note:** To keep the exact colors of an existing Material 2 theme after moving to Material 3, pass the same M2 palettes as `m2-compat` to `lf.scaffold-theme-m3()`.
211
+
212
+
213
+
214
+
215
+ ## Events
216
+ There are two ways to listen to scaffold user events (button clicks, input changes, ...):
217
+
218
+ ### (Recommended) Option 1 – Subscribe to Event Observables
219
+ Subscribe to the event Observables and listen to changes
220
+ ```ts
221
+ private scaffoldService = inject(ScaffoldService);
222
+ private router = inject(Router);
223
+
224
+ constructor() {
225
+ // Listen to click events (header menu and navbar buttons - click)
226
+ this.scaffoldService.buttonClickEventValue$.subscribe((id: string) => {
227
+ this.router.navigate([id]);
228
+ });
229
+
230
+ // Listen to header input change events (header input field - change)
231
+ this.scaffoldService.headerInputChangeValue$.subscribe((value: string) => {
232
+ ...
233
+ });
234
+ }
235
+ ```
236
+
237
+ ### Option 2 – Use Output Events
238
+ Specify the needed output events and call custom methods
239
+ ```html
240
+ <lf-scaffold
241
+ (headerButtonClickEvent)="headerButtonClickEvent($event)"
242
+ (headerInputSubmitEvent)="headerInputSubmitEvent($event)"
243
+ (headerInputChangeEvent)="headerInputChangeEvent($event)"
244
+ (navbarButtonClickEvent)="navbarButtonClickEvent($event)">
245
+ <router-outlet></router-outlet>
246
+ </lf-scaffold>
247
+ ```
248
+
249
+ ```ts
250
+ // Listen to header click events (header menu buttons - click)
251
+ public headerButtonClickEvent(id: string): void {
252
+ this.router.navigate([id]);
253
+ }
254
+
255
+ // Listen to header input submit events (header input field - submit)
256
+ public headerInputSubmitEvent(value: string): void {
257
+ ...
258
+ }
259
+
260
+ // Listen to header input change events (header input field - change)
261
+ public headerInputChangeEvent(value: string): void {
262
+ ...
263
+ }
264
+
265
+ // Listen to navbar click events (navbar menu buttons - click)
266
+ public navbarButtonClickEvent(id: string): void {
267
+ this.router.navigate([id]);
268
+ }
269
+ ```
270
+
271
+
272
+
273
+
274
+ ## (Optional) Additional Services
275
+ This library includes several utility services:
276
+
277
+ - **`Logger`** – Development-only logging
278
+ - **`SnackbarService`** – Display snackbar notifications
279
+ - **`DialogService`** – Display custom dialogs
280
+ - **`BreakpointService`** – Detect screen breakpoints
281
+ - **`ThemeService`** – Manage themes dynamically
282
+ - **`RouterService`** – Track route changes and retrieve route history
283
+ - **`SeoService`** – Manage meta tags
284
+ - **`LocalStorageService`** – Handle local storage
285
+ - **`TranslationService`** – Switch languages at runtime with per-locale JSON files
286
+
287
+ ### Logger
288
+ Logs information during development and hides all logs during production.
289
+
290
+ * **Note:** Use the provoder `provideScaffold({ production: !isDevMode() })` to disable logs in production
291
+
292
+ ```ts
293
+ import { Logger } from '@lukfel/ng-scaffold';
294
+
295
+ export class AppComponent {
296
+
297
+ private logger = inject(Logger);
298
+
299
+ // Generic api call with logging
300
+ public apiCallWithLogging(): void {
301
+ this.apiService.apiCall().then(result => {
302
+ this.logger.log(result);
303
+ }).catch(error => {
304
+ this.logger.error(error);
305
+ });
306
+ }
307
+ }
308
+ ```
309
+
310
+ ### SnackbarService
311
+ Provides basic methods to display simple snackbar notifications with or without actions.
312
+
313
+ ```ts
314
+ import { SnackbarService } from '@lukfel/ng-scaffold';
315
+
316
+ export class AppComponent {
317
+
318
+ private snackbarService = inject(SnackbarService);
319
+
320
+ // Generic api call with snackbar response
321
+ public apiCallWithSnackbarResponse(): void {
322
+ this.apiService.apiCall().then(result => {
323
+ this.snackbarService.openSnackbar('Call was successful');
324
+ }).catch(error => {
325
+ this.snackbarService.openSnackbar('Call was not successful');
326
+ });
327
+ }
328
+ }
329
+ ```
330
+
331
+ ### DialogService
332
+ Includes a basic confirmation dialog thar returns a `Promise`. Use the method `openCustomDialog` to pass your own dialog template and config.
333
+
334
+ ```ts
335
+ import { DialogService } from '@lukfel/ng-scaffold';
336
+
337
+ export class AppComponent {
338
+
339
+ private dialogService = inject(DialogService);
340
+
341
+ // Generic api call with a subsequent confirmation dialog
342
+ public apiCallWithDialogConfirmation(): void {
343
+ this.dialogService.openConfirmDialog('Do you really want to make this api call?').then(response => {
344
+ // If the user confirmed the dialog, go through with the api call
345
+ if(response === true) {
346
+ this.apiService.apiCall().then(result => {
347
+ ...
348
+ }).catch(error => {
349
+ ...
350
+ });
351
+ }
352
+ });
353
+ }
354
+ }
355
+ ```
356
+
357
+ ### BreakpointService
358
+ Allows you to subscribe to breakpoint changes.
359
+
360
+ ```ts
361
+ import { BreakpointService } from '@lukfel/ng-scaffold';
362
+
363
+ export class AppComponent {
364
+
365
+ private breakpointService = inject(BreakpointService);
366
+
367
+ constructor() {
368
+ this.breakpointService.breakpoint$.subscribe((result: BreakpointState) => {
369
+ // Check which breakpoint is active
370
+ if (result.breakpoints[Breakpoints.XSmall]) {
371
+ ...
372
+ } else if (result.breakpoints[Breakpoints.Small]) {
373
+ ...
374
+ } else if (result.breakpoints[Breakpoints.Medium]) {
375
+ ...
376
+ } else if (result.breakpoints[Breakpoints.Large]) {
377
+ ...
378
+ }
379
+ });
380
+ }
381
+ }
382
+ ```
383
+
384
+ ### ThemeService
385
+ Dynamically change between your defined themes.
386
+
387
+ * **Note:** The theme must be defined and included in your styles
388
+
389
+ ```ts
390
+ import { ThemeService } from '@lukfel/ng-scaffold';
391
+
392
+ export class AppComponent {
393
+
394
+ constructor(private themeService: ThemeService) {
395
+ this.themeService.setTheme('my-theme2', true); // set theme and store in local storage (using the built in LocalStorageService)
396
+ }
397
+ }
398
+ ```
399
+
400
+ ### TranslationService
401
+ Switches the application language at runtime by loading a separate JSON file per locale (e.g. `de.json`, `en.json`) that share the same keys. Keys missing from a locale are automatically resolved from the `fallbackLocale`.
402
+
403
+ * **Note:** Configure the available locales in the `provideScaffold` provider and serve the `<locale>.json` files from the configured `path` (default `assets/i18n/`)
404
+
405
+ ```ts
406
+ import { provideScaffold } from '@lukfel/ng-scaffold';
407
+
408
+ ...
409
+ providers: [
410
+ provideScaffold({
411
+ language: {
412
+ locales: ['de', 'en'], // available locales (also drives a language picker)
413
+ fallbackLocale: 'en', // used when a locale is unknown or a key is missing
414
+ path: 'assets/i18n/', // (Optional) base path of the <locale>.json files
415
+ persist: true // (Optional) store the active locale in local storage
416
+ }
417
+ })
418
+ ]
419
+ ```
420
+
421
+ Inject the `TranslationService` to read the translations and switch the locale. Each app keeps its own strongly-typed `TransMap` interface and reads the labels through the typed accessors.
422
+
423
+ ```ts
424
+ import { TranslationService } from '@lukfel/ng-scaffold';
425
+
426
+ export class AppComponent {
427
+
428
+ private translationService = inject(TranslationService);
429
+
430
+ // Typed signal of the active translations (empty until the first load)
431
+ public transMap = this.translationService.transMapAs<TransMap>();
432
+
433
+ // Active locale and all configured locales (e.g. for a language picker)
434
+ public locale = this.translationService.locale;
435
+ public locales = this.translationService.locales;
436
+
437
+ // Switch the active language (persisted according to the config)
438
+ public changeLanguage(locale: string): void {
439
+ this.translationService.setLocale(locale);
440
+ }
441
+ }
442
+ ```
443
+
444
+ * **Note:** For observable consumers use `locale$` and `transMap$` (or the typed `transMapStream<TransMap>()`, which only emits once translations are loaded). Track the loading state with the `loading` signal or `loading$`.
445
+
446
+
447
+
448
+
449
+ ## (Optional) Standalone Components
450
+ In addition to the scaffold components provided by default by the the `ScaffoldComponent` there are several standalone components that can be utilized.
451
+
452
+ * **Note:** Standalone components must be imported manually and are not part of the `ScaffoldComponent` import
453
+
454
+ ### List
455
+ A standalone Material Design inspired list and table hybrid component for displaying structured
456
+ collections of items. It supports avatars, titles, subtitles, actions, selections and dragging —
457
+ making it ideal for dashboards, inventories, and administrative views.
458
+
459
+ ```ts
460
+ import { ListComponent } from '@lukfel/ng-scaffold';
461
+ ```
462
+
463
+ ```ts
464
+ import { Button, ListConfig, ListHeader, ListItem } from '@lukfel/ng-scaffold';
465
+
466
+ public listConfig = signal<ListConfig>({ // (Optional) list config
467
+ enableSelection: true,
468
+ enableDragging: true,
469
+ initialSortToken: 'title',
470
+ initialSortAsc: true,
471
+ showDividers: true
472
+ });
473
+
474
+ public listHeader = signal<ListHeader>({ // (Optional) list header
475
+ matIcon: 'sort',
476
+ items: [
477
+ { title: 'Items', sortToken: 'title' }
478
+ ]
479
+ });
480
+
481
+ public listItems = signal<ListItem[]>([
482
+ { id: 1, svgIcon: 'logo', title: 'Item 2', subtitle: 'I am disabled', disabled: true },
483
+ { id: 0, avatar: 'img/logos/ic_launcher-web.png', title: 'Item 1', subtitle: 'I am clickable', clickable: true },
484
+ { id: 2, matIcon: 'person', title: 'Item 3', subtitle: 'I have no edit buton', hiddenButtonIds: ['edit'] },
485
+ ]);
486
+
487
+ public listButtons = signal<Button[]>([ // (Optional) list buttons
488
+ { id: 'edit', matIcon: 'edit' },
489
+ { id: 'delete', matIcon: 'delete', cssClass: 'warn' }
490
+ ]);
491
+
492
+ // (Optional) Handle sort events
493
+ public onListSortChange(event: { sortToken: string, sortAsc: boolean }): void {
494
+ if (event?.sortToken === 'title') {
495
+ this.listItems().sort((a, b) => {
496
+ if (!a.title || !b.title) return 0;
497
+ if (event.sortAsc) return a.title.localeCompare(b.title);
498
+ return b.title.localeCompare(a.title);
499
+ });
500
+ }
501
+ }
502
+
503
+ public onListSelectionChange(items: ListItem[]): void {
504
+ // handle selection change
505
+ }
506
+
507
+ public onListButtonClick(event: { buttonId: string, item: ListItem }): void {
508
+ // handle button click
509
+ }
510
+
511
+ public onListItemClick(item: ListItem): void {
512
+ // handle item click
513
+ }
514
+ ```
515
+
516
+ ```html
517
+ <lf-list
518
+ [config]="listConfig()"
519
+ [header]="listHeader()"
520
+ [items]="listItems()"
521
+ [buttons]="listButtons()"
522
+ (sortChangeEvent)="onListSortChange($event)"
523
+ (selectionChangeEvent)="onListSelectionChange()"
524
+ (buttonClickEvent)="onListButtonClick($event)"
525
+ (itemClickEvent)="onListItemClick($event)"></lf-list>
526
+ ```
527
+
528
+ ### File-Upload
529
+ A standalone Material Design styled button for selecting and uploading files. It wraps a hidden
530
+ native file input and exposes a simple, customizable interface with built-in icon, tooltip, and
531
+ state management.
532
+
533
+ ```ts
534
+ import { FileUploadComponent } from '@lukfel/ng-scaffold';
535
+ ```
536
+
537
+ ```ts
538
+ public onFileChange(file: File): void {
539
+ // handle file upload
540
+ }
541
+ ```
542
+
543
+ ```html
544
+ <lf-file-upload
545
+ color="accent"
546
+ label="Upload File"
547
+ matIcon="upload"
548
+ accept="*"
549
+ (fileChangeEvent)="onFileChange($event)"></lf-file-upload>
550
+ ```
551
+
552
+ ### Color-Picker
553
+ A standalone Material Design styled button for picking colors (uses native design on Safari). It wraps a hidden native color
554
+ input and exposes a simple, customizable interface with built-in icon, tooltip, and state
555
+ management.
556
+
557
+ ```ts
558
+ import { ColorPickerComponent } from '@lukfel/ng-scaffold';
559
+ ```
560
+
561
+ ```ts
562
+ public onColorChange(color: string): void {
563
+ // handle color pick
564
+ }
565
+ ```
566
+
567
+ ```html
568
+ <lf-color-picker
569
+ color="accent"
570
+ label="Pick Color"
571
+ matIcon="color_lens"
572
+ (colorChangeEvent)="onColorChange($event)"></lf-color-picker>
573
+ ```
574
+
575
+ ### Placeholder
576
+ A standalone UI component designed to display an informative placeholder or empty state when no
577
+ data is available. It provides a structured layout for an icon, heading, message, and optional
578
+ action — helping guide users toward the next step.
579
+
580
+ ```ts
581
+ import { PlaceholderComponent } from '@lukfel/ng-scaffold';
582
+ ```
583
+
584
+ ```ts
585
+ import { PlaceholderConfig } from '@lukfel/ng-scaffold';
586
+
587
+ public placeholderConfig = signal<PlaceholderConfig>({
588
+ matIcon: 'widgets',
589
+ title: 'Title',
590
+ message: 'This is a placeholder message.',
591
+ button: {
592
+ id: 'placeholder',
593
+ label: 'ACTION'
594
+ }
595
+ });
596
+
597
+ public onPlaceholderButtonClick(): void {
598
+ // handle placeholder click
599
+ }
600
+ ```
601
+
602
+ ```html
603
+ <lf-placeholder
604
+ [placeholderConfig]="placeholderConfig()"
605
+ (buttonClickEvent)="onPlaceholderButtonClick()"></lf-placeholder>
606
+ ```
607
+
608
+ ### Notification
609
+ A standalone standalone Material Design inspired notification component for displaying transient
610
+ messages to users. It supports different severity levels (primary, accent, warn), optional
611
+ icons, optional actions, and links.
612
+
613
+ ```ts
614
+ import { NotificationComponent } from '@lukfel/ng-scaffold';
615
+ ```
616
+
617
+ ```html
618
+ <lf-notification
619
+ [static]="true"
620
+ color="accent"
621
+ message="This is an info notification with action."
622
+ matIcon="info"
623
+ action="Action"
624
+ (clickEvent)="onNotificationClick()"></lf-notification>
625
+ ```
626
+
627
+
628
+
629
+
630
+ ## (Optional) Interceptors
631
+ Intercept HTTP Calls and automatically show a loading spinner.
632
+
633
+ * **Note:** The loading spinner can also be manually shown by udpating the value for `scaffoldConfig.loading` in the `ScaffoldService`
634
+
635
+ ```ts
636
+ import { ScaffoldLoadingInterceptor } from '@lukfel/ng-scaffold';
637
+
638
+ ...
639
+ providers: [
640
+ {
641
+ provide: HTTP_INTERCEPTORS,
642
+ useClass: ScaffoldLoadingInterceptor,
643
+ multi: true
644
+ }
645
+ ]
644
646
  ```