ui-ux-consultant-cli 1.0.0-beta.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.
Files changed (30) hide show
  1. package/assets/ui-ux-consultant/SKILL.md +844 -0
  2. package/assets/ui-ux-consultant/references/accessibility.md +175 -0
  3. package/assets/ui-ux-consultant/references/alt-libraries.md +90 -0
  4. package/assets/ui-ux-consultant/references/animations.md +448 -0
  5. package/assets/ui-ux-consultant/references/catalog/colors.md +91 -0
  6. package/assets/ui-ux-consultant/references/catalog/fonts.md +363 -0
  7. package/assets/ui-ux-consultant/references/catalog/products.md +340 -0
  8. package/assets/ui-ux-consultant/references/catalog/styles.md +165 -0
  9. package/assets/ui-ux-consultant/references/components.md +1116 -0
  10. package/assets/ui-ux-consultant/references/patterns.md +600 -0
  11. package/assets/ui-ux-consultant/references/performance.md +198 -0
  12. package/assets/ui-ux-consultant/references/stacks/astro.md +382 -0
  13. package/assets/ui-ux-consultant/references/stacks/flutter.md +308 -0
  14. package/assets/ui-ux-consultant/references/stacks/html-tailwind.md +415 -0
  15. package/assets/ui-ux-consultant/references/stacks/jetpack-compose.md +333 -0
  16. package/assets/ui-ux-consultant/references/stacks/laravel.md +521 -0
  17. package/assets/ui-ux-consultant/references/stacks/nextjs.md +275 -0
  18. package/assets/ui-ux-consultant/references/stacks/nuxt-ui.md +384 -0
  19. package/assets/ui-ux-consultant/references/stacks/nuxtjs.md +264 -0
  20. package/assets/ui-ux-consultant/references/stacks/react-native.md +346 -0
  21. package/assets/ui-ux-consultant/references/stacks/react.md +268 -0
  22. package/assets/ui-ux-consultant/references/stacks/shadcn.md +485 -0
  23. package/assets/ui-ux-consultant/references/stacks/svelte.md +429 -0
  24. package/assets/ui-ux-consultant/references/stacks/swiftui.md +336 -0
  25. package/assets/ui-ux-consultant/references/stacks/threejs.md +366 -0
  26. package/assets/ui-ux-consultant/references/stacks/vue.md +272 -0
  27. package/assets/ui-ux-consultant/references/theming.md +701 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +130 -0
  30. package/package.json +51 -0
@@ -0,0 +1,1116 @@
1
+ # Angular Material 3 + CDK Component Catalog
2
+
3
+ A reference catalog for Angular Material 3 and CDK components. Each entry covers purpose, usage guidance, selector, key inputs/outputs, accessibility notes, and a minimal code example.
4
+
5
+ ---
6
+
7
+ ## Actions
8
+
9
+ ### Button Variants (`mat-button`)
10
+
11
+ Angular Material provides six button variants mapped to M3 emphasis levels:
12
+
13
+ | Directive | M3 Variant | Emphasis | Use When |
14
+ |---|---|---|---|
15
+ | `mat-flat-button` | Filled | Highest | Primary CTA, one per screen |
16
+ | `mat-button` (with color) | Tonal | High | Secondary actions |
17
+ | `mat-raised-button` | Elevated | Medium | Actions needing separation from background |
18
+ | `mat-stroked-button` | Outlined | Low | Alternative actions, destructive confirmations |
19
+ | `mat-button` | Text | Lowest | Tertiary, inline, cancel buttons |
20
+ | `mat-icon-button` | Icon | — | Toolbar actions, compact spaces |
21
+ | `mat-fab` | FAB | — | Single primary action per view (mobile-first) |
22
+ | `mat-mini-fab` | Mini FAB | — | FAB in constrained spaces |
23
+
24
+ **M3 Emphasis Rule:** Use at most one filled button per screen region. Combine tonal + text for secondary + cancel. Never stack two filled buttons side by side.
25
+
26
+ **Key Inputs:**
27
+ - `color`: `'primary'` | `'accent'` | `'warn'`
28
+ - `disabled`: boolean
29
+ - `disableRipple`: boolean (avoid — ripple is an accessibility affordance)
30
+
31
+ **Accessibility:**
32
+ - All button variants are `<button>` elements — keyboard and screen reader accessible by default.
33
+ - `mat-icon-button` must have `aria-label` since it has no visible text.
34
+ - Never disable without explaining why (use `matTooltip` on the wrapper).
35
+
36
+ ```html
37
+ <!-- Primary action -->
38
+ <button mat-flat-button color="primary" (click)="save()">
39
+ <mat-icon>save</mat-icon> Save
40
+ </button>
41
+
42
+ <!-- Secondary action -->
43
+ <button mat-stroked-button (click)="cancel()">Cancel</button>
44
+
45
+ <!-- Icon-only toolbar button -->
46
+ <button mat-icon-button aria-label="Delete item" (click)="delete()">
47
+ <mat-icon>delete</mat-icon>
48
+ </button>
49
+
50
+ <!-- FAB -->
51
+ <button mat-fab color="primary" aria-label="Add item" (click)="add()">
52
+ <mat-icon>add</mat-icon>
53
+ </button>
54
+ ```
55
+
56
+ ```typescript
57
+ import { MatButtonModule } from '@angular/material/button';
58
+ import { MatIconModule } from '@angular/material/icon';
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Forms & Inputs
64
+
65
+ ### `mat-form-field` + `matInput`
66
+
67
+ The universal wrapper for text inputs, textareas, and select. Always pair with `mat-label` and `mat-error`.
68
+
69
+ **Appearance variants:** `fill` (default M3), `outline`
70
+
71
+ **When to use:** Any text entry. Do not place `mat-select` or `mat-autocomplete` outside a form field.
72
+
73
+ **Key Inputs:**
74
+ - `appearance`: `'fill'` | `'outline'`
75
+ - `floatLabel`: `'always'` | `'auto'` (default)
76
+ - `subscriptSizing`: `'fixed'` | `'dynamic'` (dynamic avoids layout shift when no hint/error)
77
+ - `matInput` directive: standard HTML input attributes (`type`, `placeholder`, `required`, `minlength`)
78
+
79
+ **Accessibility:** `mat-label` is automatically linked via `aria-labelledby`. Always include `mat-error` with meaningful messages — screen readers announce them on blur.
80
+
81
+ ```html
82
+ <mat-form-field appearance="outline">
83
+ <mat-label>Email</mat-label>
84
+ <input matInput type="email" [formControl]="emailCtrl" required />
85
+ <mat-hint>We'll never share your email.</mat-hint>
86
+ <mat-error *ngIf="emailCtrl.hasError('required')">Email is required.</mat-error>
87
+ <mat-error *ngIf="emailCtrl.hasError('email')">Enter a valid email.</mat-error>
88
+ </mat-form-field>
89
+ ```
90
+
91
+ ---
92
+
93
+ ### `mat-select` + `mat-option`
94
+
95
+ Dropdown select. Use for ≤10 options with known values. For search-as-you-type or >10 options, use `mat-autocomplete`.
96
+
97
+ **Key Inputs:**
98
+ - `multiple`: boolean — enables multi-select
99
+ - `[compareWith]`: function — for object values
100
+ - `panelClass`: string — style the dropdown panel
101
+
102
+ **Outputs:** `(selectionChange)`: `MatSelectChange`
103
+
104
+ **Accessibility:** Implements ARIA `listbox`/`option` roles. Always inside `mat-form-field`.
105
+
106
+ ```html
107
+ <mat-form-field appearance="outline">
108
+ <mat-label>Country</mat-label>
109
+ <mat-select [formControl]="countryCtrl">
110
+ <mat-option value="">-- Select --</mat-option>
111
+ <mat-option *ngFor="let c of countries" [value]="c.code">
112
+ {{ c.name }}
113
+ </mat-option>
114
+ </mat-select>
115
+ <mat-error *ngIf="countryCtrl.hasError('required')">Required.</mat-error>
116
+ </mat-form-field>
117
+ ```
118
+
119
+ ---
120
+
121
+ ### `mat-autocomplete`
122
+
123
+ Search-as-you-type dropdown. Requires `[matAutocomplete]` binding on an `matInput` and an `async` pipe or manual subscription to filter options.
124
+
125
+ **When to use:** >10 options, freeform + suggestion combined, or when options depend on search query.
126
+
127
+ **Key Inputs:**
128
+ - `[displayWith]`: function to convert option value to display string
129
+ - `autoSelectActiveOption`: boolean
130
+ - `panelWidth`: `'auto'` | string
131
+
132
+ **Outputs:** `(optionSelected)`: `MatAutocompleteSelectedEvent`
133
+
134
+ ```html
135
+ <mat-form-field appearance="outline">
136
+ <mat-label>City</mat-label>
137
+ <input matInput [formControl]="cityCtrl" [matAutocomplete]="auto" />
138
+ <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayCity">
139
+ <mat-option *ngFor="let city of filteredCities$ | async" [value]="city">
140
+ {{ city.name }}, {{ city.country }}
141
+ </mat-option>
142
+ </mat-autocomplete>
143
+ </mat-form-field>
144
+ ```
145
+
146
+ ```typescript
147
+ filteredCities$ = this.cityCtrl.valueChanges.pipe(
148
+ startWith(''),
149
+ map(v => this.filter(v ?? ''))
150
+ );
151
+ ```
152
+
153
+ ---
154
+
155
+ ### `mat-checkbox`
156
+
157
+ Boolean form field. Use for independent true/false choices or multi-select lists.
158
+
159
+ **Do not use** for mutually exclusive choices — use `mat-radio-group` instead.
160
+
161
+ **Key Inputs:**
162
+ - `[checked]`: boolean
163
+ - `[indeterminate]`: boolean (for "select all" parent states)
164
+ - `[labelPosition]`: `'before'` | `'after'`
165
+ - `[formControl]`
166
+
167
+ **Outputs:** `(change)`: `MatCheckboxChange`
168
+
169
+ **Accessibility:** Renders as `<input type="checkbox">`. Indeterminate state is conveyed via `aria-checked="mixed"`.
170
+
171
+ ```html
172
+ <mat-checkbox [formControl]="agreeCtrl" labelPosition="after">
173
+ I agree to the <a href="/terms">Terms of Service</a>
174
+ </mat-checkbox>
175
+ ```
176
+
177
+ ---
178
+
179
+ ### `mat-radio-group` + `mat-radio-button`
180
+
181
+ Exclusive selection from a small set. Use when choices are ≤5 and all options should be visible simultaneously.
182
+
183
+ **Key Inputs on group:**
184
+ - `[value]`: selected value
185
+ - `[formControl]`
186
+ - `name`: auto-generated but can be overridden
187
+
188
+ **Key Inputs on button:**
189
+ - `[value]`: option value
190
+ - `[disabled]`: boolean per option
191
+
192
+ **Accessibility:** Implements ARIA `radiogroup`/`radio`. Group must have an accessible name via `aria-label` or `aria-labelledby`.
193
+
194
+ ```html
195
+ <label id="plan-label">Billing Plan</label>
196
+ <mat-radio-group [formControl]="planCtrl" aria-labelledby="plan-label">
197
+ <mat-radio-button value="monthly">Monthly</mat-radio-button>
198
+ <mat-radio-button value="annual">Annual (save 20%)</mat-radio-button>
199
+ <mat-radio-button value="lifetime">Lifetime</mat-radio-button>
200
+ </mat-radio-group>
201
+ ```
202
+
203
+ ---
204
+
205
+ ### `mat-slide-toggle`
206
+
207
+ Binary setting that takes effect immediately (no form submission needed). Distinguish from checkbox: use slide-toggle for settings panels, checkbox for form fields.
208
+
209
+ **Key Inputs:**
210
+ - `[formControl]`
211
+ - `[checked]`: boolean
212
+ - `color`: `'primary'` | `'accent'` | `'warn'`
213
+ - `labelPosition`: `'before'` | `'after'`
214
+
215
+ **Outputs:** `(change)`: `MatSlideToggleChange`
216
+
217
+ ```html
218
+ <mat-slide-toggle [formControl]="notificationsCtrl" color="primary">
219
+ Push notifications
220
+ </mat-slide-toggle>
221
+ ```
222
+
223
+ ---
224
+
225
+ ### `mat-datepicker`
226
+
227
+ Date input with calendar popup. Always set `[min]` and `[max]` to guide users. Always include `mat-datepicker-toggle`.
228
+
229
+ **Key Inputs:**
230
+ - `[min]`: Date
231
+ - `[max]`: Date
232
+ - `[startView]`: `'month'` | `'year'` | `'multi-year'`
233
+ - `[dateFilter]`: function to disable specific dates
234
+ - `touchUi`: boolean (mobile-friendly full-screen picker)
235
+
236
+ **Accessibility:** Requires `MatDatepickerModule` and a `DateAdapter`. Calendar is keyboard navigable with arrow keys.
237
+
238
+ ```html
239
+ <mat-form-field appearance="outline">
240
+ <mat-label>Date of Birth</mat-label>
241
+ <input matInput [matDatepicker]="picker" [formControl]="dobCtrl"
242
+ [max]="maxDate" />
243
+ <mat-datepicker-toggle matIconSuffix [for]="picker" />
244
+ <mat-datepicker #picker startView="multi-year" />
245
+ <mat-error *ngIf="dobCtrl.hasError('matDatepickerMax')">
246
+ Date must be in the past.
247
+ </mat-error>
248
+ </mat-form-field>
249
+ ```
250
+
251
+ ---
252
+
253
+ ### `mat-slider`
254
+
255
+ Numeric range input. Use for approximate values where exact input isn't critical (volume, opacity, price range). Always display the current value.
256
+
257
+ **Key Inputs:**
258
+ - `[min]`: number (default 0)
259
+ - `[max]`: number (default 100)
260
+ - `[step]`: number
261
+ - `discrete`: boolean (shows value bubble on drag)
262
+ - `showTickMarks`: boolean
263
+
264
+ **Accessibility:** Renders as `<input type="range">`. Always pair with a visible label.
265
+
266
+ ```html
267
+ <label id="vol-label">Volume: {{ volumeCtrl.value }}</label>
268
+ <mat-slider min="0" max="100" step="5" discrete aria-labelledby="vol-label">
269
+ <input matSliderThumb [formControl]="volumeCtrl" />
270
+ </mat-slider>
271
+ ```
272
+
273
+ ---
274
+
275
+ ### `mat-chips` + `mat-chip-grid`
276
+
277
+ Tag input for multi-select with search. Use `mat-chip-grid` for interactive chip inputs; use `mat-chip-listbox` for selection chips; use `mat-chip-set` for display-only.
278
+
279
+ **Key Outputs:**
280
+ - `(removed)`: `MatChipEvent` on chip grid
281
+ - `(chipEnd)`: `MatChipInputEvent` from `[matChipInputFor]`
282
+
283
+ ```html
284
+ <mat-form-field appearance="outline">
285
+ <mat-label>Tags</mat-label>
286
+ <mat-chip-grid #chipGrid>
287
+ <mat-chip-row *ngFor="let tag of tags" (removed)="remove(tag)">
288
+ {{ tag }}
289
+ <button matChipRemove aria-label="Remove {{ tag }}">
290
+ <mat-icon>cancel</mat-icon>
291
+ </button>
292
+ </mat-chip-row>
293
+ </mat-chip-grid>
294
+ <input placeholder="Add tag..." [matChipInputFor]="chipGrid"
295
+ (matChipInputTokenEnd)="add($event)" />
296
+ </mat-form-field>
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Navigation
302
+
303
+ ### `mat-toolbar`
304
+
305
+ App bar / top navigation bar. For persistent navigation, use `position: sticky; top: 0` in CSS.
306
+
307
+ **Key Inputs:**
308
+ - `color`: `'primary'` | `'accent'` | `'warn'`
309
+
310
+ ```html
311
+ <mat-toolbar color="primary" class="app-toolbar">
312
+ <button mat-icon-button aria-label="Open menu" (click)="sidenav.toggle()">
313
+ <mat-icon>menu</mat-icon>
314
+ </button>
315
+ <span class="toolbar-title">My App</span>
316
+ <span class="spacer"></span>
317
+ <button mat-icon-button aria-label="User account">
318
+ <mat-icon>account_circle</mat-icon>
319
+ </button>
320
+ </mat-toolbar>
321
+ ```
322
+
323
+ ```scss
324
+ .app-toolbar { position: sticky; top: 0; z-index: 1000; }
325
+ .spacer { flex: 1; }
326
+ ```
327
+
328
+ ---
329
+
330
+ ### `mat-sidenav-container` + `mat-sidenav` + `mat-sidenav-content`
331
+
332
+ Side navigation layout. Handles desktop persistent nav and mobile overlay nav.
333
+
334
+ **`mat-sidenav` Key Inputs:**
335
+ - `mode`: `'over'` (overlay, default mobile) | `'side'` (pushes content, desktop) | `'push'`
336
+ - `opened`: boolean
337
+ - `position`: `'start'` | `'end'`
338
+ - `fixedInViewport`: boolean (sticky sidenav regardless of scroll)
339
+
340
+ **Outputs:** `(opened)`, `(closed)`, `(openedChange)`
341
+
342
+ **Responsive pattern — switch mode based on viewport:**
343
+
344
+ ```typescript
345
+ // component.ts
346
+ readonly isHandset$ = inject(BreakpointObserver)
347
+ .observe(Breakpoints.Handset)
348
+ .pipe(map(r => r.matches), shareReplay());
349
+ ```
350
+
351
+ ```html
352
+ <mat-sidenav-container>
353
+ <mat-sidenav #sidenav
354
+ [mode]="(isHandset$ | async) ? 'over' : 'side'"
355
+ [opened]="!(isHandset$ | async)">
356
+ <mat-nav-list>
357
+ <a mat-list-item routerLink="/dashboard" routerLinkActive="active">
358
+ <mat-icon matListItemIcon>dashboard</mat-icon>
359
+ <span matListItemTitle>Dashboard</span>
360
+ </a>
361
+ <a mat-list-item routerLink="/settings" routerLinkActive="active">
362
+ <mat-icon matListItemIcon>settings</mat-icon>
363
+ <span matListItemTitle>Settings</span>
364
+ </a>
365
+ </mat-nav-list>
366
+ </mat-sidenav>
367
+ <mat-sidenav-content>
368
+ <router-outlet />
369
+ </mat-sidenav-content>
370
+ </mat-sidenav-container>
371
+ ```
372
+
373
+ ---
374
+
375
+ ### `mat-tab-group` + `mat-tab`
376
+
377
+ Horizontal tabs for switching between related views. Keep ≤5 tabs visible; use `mat-tab-nav-bar` for router-linked tabs.
378
+
379
+ **Key Inputs on group:**
380
+ - `[selectedIndex]`
381
+ - `animationDuration`: `'0ms'` to disable animation
382
+ - `mat-stretch-tabs`: boolean (fills full width)
383
+ - `color`, `backgroundColor`
384
+
385
+ **Outputs:** `(selectedTabChange)`: `MatTabChangeEvent`
386
+
387
+ ```html
388
+ <mat-tab-group [selectedIndex]="activeTab" (selectedIndexChange)="activeTab = $event">
389
+ <mat-tab label="Overview">
390
+ <ng-template matTabContent> <!-- lazy loaded -->
391
+ <app-overview />
392
+ </ng-template>
393
+ </mat-tab>
394
+ <mat-tab label="Analytics">
395
+ <ng-template matTabContent>
396
+ <app-analytics />
397
+ </ng-template>
398
+ </mat-tab>
399
+ </mat-tab-group>
400
+ ```
401
+
402
+ ---
403
+
404
+ ### `mat-stepper`
405
+
406
+ Multi-step wizard. Use `linear` for guided flows where each step must be completed before proceeding.
407
+
408
+ **Key Inputs:**
409
+ - `linear`: boolean — validates before allowing next step
410
+ - `orientation`: `'horizontal'` | `'vertical'`
411
+ - `[selectedIndex]`
412
+
413
+ **Step Inputs:**
414
+ - `[stepControl]`: AbstractControl — form group for step validation
415
+ - `label`: string or `matStepLabel` template
416
+ - `optional`: boolean
417
+ - `editable`: boolean
418
+
419
+ ```html
420
+ <mat-stepper linear orientation="vertical" #stepper>
421
+ <mat-step [stepControl]="accountForm" label="Account">
422
+ <form [formGroup]="accountForm">
423
+ <mat-form-field appearance="outline">
424
+ <mat-label>Email</mat-label>
425
+ <input matInput formControlName="email" type="email" />
426
+ </mat-form-field>
427
+ <div>
428
+ <button mat-flat-button color="primary" matStepperNext>Next</button>
429
+ </div>
430
+ </form>
431
+ </mat-step>
432
+ <mat-step [stepControl]="profileForm" label="Profile">
433
+ <form [formGroup]="profileForm">
434
+ <!-- fields -->
435
+ <button mat-stroked-button matStepperPrevious>Back</button>
436
+ <button mat-flat-button color="primary" matStepperNext>Next</button>
437
+ </form>
438
+ </mat-step>
439
+ <mat-step label="Confirm">
440
+ <button mat-flat-button color="primary" (click)="submit()">Submit</button>
441
+ </mat-step>
442
+ </mat-stepper>
443
+ ```
444
+
445
+ ---
446
+
447
+ ### `mat-menu` + `mat-menu-item`
448
+
449
+ Contextual menus triggered by user action. Never use for primary navigation.
450
+
451
+ **Key Inputs on menu:**
452
+ - `xPosition`: `'before'` | `'after'`
453
+ - `yPosition`: `'above'` | `'below'`
454
+ - `overlapTrigger`: boolean
455
+
456
+ **Trigger directive:** `[matMenuTriggerFor]="menu"` on any element.
457
+
458
+ **Accessibility:** Implements ARIA `menu`/`menuitem`. Keyboard: Enter/Space to open, arrows to navigate, Escape to close.
459
+
460
+ ```html
461
+ <button mat-icon-button [matMenuTriggerFor]="actionsMenu" aria-label="More actions">
462
+ <mat-icon>more_vert</mat-icon>
463
+ </button>
464
+
465
+ <mat-menu #actionsMenu>
466
+ <button mat-menu-item (click)="edit()">
467
+ <mat-icon>edit</mat-icon> Edit
468
+ </button>
469
+ <button mat-menu-item (click)="duplicate()">
470
+ <mat-icon>content_copy</mat-icon> Duplicate
471
+ </button>
472
+ <mat-divider />
473
+ <button mat-menu-item class="danger" (click)="delete()">
474
+ <mat-icon>delete</mat-icon> Delete
475
+ </button>
476
+ </mat-menu>
477
+ ```
478
+
479
+ ---
480
+
481
+ ## Data Display
482
+
483
+ ### `mat-table` + `matColumnDef`
484
+
485
+ Feature-rich data table. Always implement `trackBy` for performance. Provide loading and empty states.
486
+
487
+ **Directives:**
488
+ - `matColumnDef`: defines a column
489
+ - `matHeaderCellDef`, `matCellDef`, `matFooterCellDef`: cell templates
490
+ - `matHeaderRowDef`, `matRowDef`: row templates
491
+ - `matSort`: add to table for sortable columns
492
+ - `mat-sort-header`: add to `<th>` to make sortable
493
+
494
+ **Key Inputs:**
495
+ - `[dataSource]`: array, Observable, or `MatTableDataSource`
496
+ - `[trackBy]`: function
497
+ - `multiTemplateDataRows`: boolean (expandable rows)
498
+
499
+ **Accessibility:** Renders as `<table>` with proper `<thead>`, `<tbody>`. Sort headers announce direction changes.
500
+
501
+ ```typescript
502
+ // component.ts
503
+ dataSource = new MatTableDataSource<User>();
504
+ displayedColumns = ['name', 'email', 'role', 'actions'];
505
+ isLoading = signal(true);
506
+
507
+ @ViewChild(MatSort) sort!: MatSort;
508
+ @ViewChild(MatPaginator) paginator!: MatPaginator;
509
+
510
+ ngAfterViewInit() {
511
+ this.dataSource.sort = this.sort;
512
+ this.dataSource.paginator = this.paginator;
513
+ }
514
+
515
+ trackByUserId = (_: number, user: User) => user.id;
516
+ ```
517
+
518
+ ```html
519
+ <div class="table-container">
520
+ <!-- Loading -->
521
+ <mat-progress-bar *ngIf="isLoading()" mode="indeterminate" />
522
+
523
+ <table mat-table [dataSource]="dataSource" matSort [trackBy]="trackByUserId">
524
+
525
+ <!-- Name Column -->
526
+ <ng-container matColumnDef="name">
527
+ <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
528
+ <td mat-cell *matCellDef="let row">{{ row.name }}</td>
529
+ </ng-container>
530
+
531
+ <!-- Email Column -->
532
+ <ng-container matColumnDef="email">
533
+ <th mat-header-cell *matHeaderCellDef mat-sort-header>Email</th>
534
+ <td mat-cell *matCellDef="let row">{{ row.email }}</td>
535
+ </ng-container>
536
+
537
+ <!-- Actions Column -->
538
+ <ng-container matColumnDef="actions" stickyEnd>
539
+ <th mat-header-cell *matHeaderCellDef></th>
540
+ <td mat-cell *matCellDef="let row">
541
+ <button mat-icon-button (click)="edit(row)" aria-label="Edit">
542
+ <mat-icon>edit</mat-icon>
543
+ </button>
544
+ </td>
545
+ </ng-container>
546
+
547
+ <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
548
+ <tr mat-row *matRowDef="let row; columns: displayedColumns;"
549
+ [class.selected]="selectedRow === row"
550
+ (click)="select(row)"></tr>
551
+
552
+ <!-- Empty state -->
553
+ <tr class="mat-row" *matNoDataRow>
554
+ <td class="mat-cell empty-state" [attr.colspan]="displayedColumns.length">
555
+ No records found.
556
+ </td>
557
+ </tr>
558
+ </table>
559
+
560
+ <mat-paginator [pageSizeOptions]="[10, 25, 50]" showFirstLastButtons />
561
+ </div>
562
+ ```
563
+
564
+ ---
565
+
566
+ ### `mat-paginator`
567
+
568
+ Pagination controls. Always pair with `MatTableDataSource` or handle `(page)` events manually for server-side pagination.
569
+
570
+ **Key Inputs:**
571
+ - `[length]`: total item count (required for server-side)
572
+ - `[pageSize]`: default page size
573
+ - `[pageSizeOptions]`: number[]
574
+ - `showFirstLastButtons`: boolean
575
+ - `[pageIndex]`: current page (0-based)
576
+
577
+ **Outputs:** `(page)`: `PageEvent`
578
+
579
+ ---
580
+
581
+ ### `mat-list` + `mat-list-item`
582
+
583
+ Simple item lists. Use `mat-nav-list` for lists of links. Use `mat-action-list` for clickable non-link items.
584
+
585
+ **Slot directives on list-item:**
586
+ - `matListItemTitle`: primary text
587
+ - `matListItemLine`: secondary/tertiary text (up to 3)
588
+ - `matListItemIcon`: leading icon
589
+ - `matListItemAvatar`: leading avatar
590
+ - `[matListItemMeta]`: trailing content
591
+
592
+ ```html
593
+ <mat-list>
594
+ <mat-list-item *ngFor="let file of files">
595
+ <mat-icon matListItemIcon>insert_drive_file</mat-icon>
596
+ <span matListItemTitle>{{ file.name }}</span>
597
+ <span matListItemLine>{{ file.size | filesize }} · {{ file.modified | date }}</span>
598
+ <button matListItemMeta mat-icon-button aria-label="Download">
599
+ <mat-icon>download</mat-icon>
600
+ </button>
601
+ </mat-list-item>
602
+ </mat-list>
603
+ ```
604
+
605
+ ---
606
+
607
+ ### `mat-card`
608
+
609
+ Content container. Use for grouping related information. Do not overuse — cards add visual weight.
610
+
611
+ **Sub-components:**
612
+ - `mat-card-header`: title row (use `mat-card-title`, `mat-card-subtitle`)
613
+ - `mat-card-content`: main body (padded)
614
+ - `mat-card-actions`: button row (use `align="end"`)
615
+ - `mat-card-footer`: bottom metadata
616
+
617
+ **Inputs:** `appearance`: `'raised'` | `'outlined'` | `'filled'`
618
+
619
+ ```html
620
+ <mat-card appearance="outlined">
621
+ <mat-card-header>
622
+ <img mat-card-avatar src="{{ user.avatar }}" alt="{{ user.name }}" />
623
+ <mat-card-title>{{ user.name }}</mat-card-title>
624
+ <mat-card-subtitle>{{ user.role }}</mat-card-subtitle>
625
+ </mat-card-header>
626
+ <mat-card-content>
627
+ <p>{{ user.bio }}</p>
628
+ </mat-card-content>
629
+ <mat-card-actions align="end">
630
+ <button mat-stroked-button>View Profile</button>
631
+ <button mat-flat-button color="primary">Message</button>
632
+ </mat-card-actions>
633
+ </mat-card>
634
+ ```
635
+
636
+ ---
637
+
638
+ ### `mat-expansion-panel`
639
+
640
+ Accordion panels for progressive disclosure. Group with `mat-accordion` to enforce single-open behavior.
641
+
642
+ **Key Inputs on panel:**
643
+ - `expanded`: boolean
644
+ - `disabled`: boolean
645
+ - `hideToggle`: boolean
646
+
647
+ **Key Inputs on accordion:**
648
+ - `multi`: boolean (allow multiple panels open; default false)
649
+ - `displayMode`: `'default'` | `'flat'`
650
+
651
+ **Outputs:** `(opened)`, `(closed)`, `(expandedChange)`
652
+
653
+ ```html
654
+ <mat-accordion>
655
+ <mat-expansion-panel *ngFor="let section of sections">
656
+ <mat-expansion-panel-header>
657
+ <mat-panel-title>{{ section.title }}</mat-panel-title>
658
+ <mat-panel-description>{{ section.summary }}</mat-panel-description>
659
+ </mat-expansion-panel-header>
660
+ <p>{{ section.content }}</p>
661
+ </mat-expansion-panel>
662
+ </mat-accordion>
663
+ ```
664
+
665
+ ---
666
+
667
+ ### `mat-badge`
668
+
669
+ Notification count overlaid on icons or buttons. Use for unread counts, cart quantities, or status indicators.
670
+
671
+ **Directive:** `[matBadge]` applied to host element.
672
+
673
+ **Key Inputs:**
674
+ - `[matBadge]`: string | number (the badge content)
675
+ - `matBadgeColor`: `'primary'` | `'accent'` | `'warn'`
676
+ - `matBadgePosition`: `'above after'` | `'above before'` | `'below after'` | `'below before'`
677
+ - `[matBadgeHidden]`: boolean (hide when count is 0)
678
+ - `matBadgeSize`: `'small'` | `'medium'` | `'large'`
679
+
680
+ **Accessibility:** Set `matBadgeDescription` for screen readers (e.g., `"3 unread notifications"`).
681
+
682
+ ```html
683
+ <button mat-icon-button
684
+ [matBadge]="unreadCount"
685
+ [matBadgeHidden]="unreadCount === 0"
686
+ matBadgeColor="warn"
687
+ matBadgeDescription="{{ unreadCount }} unread notifications"
688
+ aria-label="Notifications">
689
+ <mat-icon>notifications</mat-icon>
690
+ </button>
691
+ ```
692
+
693
+ ---
694
+
695
+ ### `mat-chip` (Display)
696
+
697
+ Read-only tags/categories. For interactive chips, use `mat-chip-listbox`; for input, use `mat-chip-grid`.
698
+
699
+ ```html
700
+ <!-- Category tags -->
701
+ <mat-chip-set aria-label="Article categories">
702
+ <mat-chip *ngFor="let tag of article.tags">{{ tag }}</mat-chip>
703
+ </mat-chip-set>
704
+
705
+ <!-- Status chip with icon -->
706
+ <mat-chip color="primary" highlighted>
707
+ <mat-icon matChipAvatar>check_circle</mat-icon>
708
+ Active
709
+ </mat-chip>
710
+ ```
711
+
712
+ ---
713
+
714
+ ### `mat-divider`
715
+
716
+ Visual separator. Use sparingly — whitespace is usually preferable. Use `inset` for list dividers that align with text.
717
+
718
+ ```html
719
+ <mat-divider /> <!-- full-width -->
720
+ <mat-divider inset /> <!-- inset (aligns with list text) -->
721
+ <mat-divider vertical /> <!-- vertical (in flex containers) -->
722
+ ```
723
+
724
+ ---
725
+
726
+ ## Feedback & Overlays
727
+
728
+ ### `MatDialog`
729
+
730
+ Modal dialogs for focused user tasks. Inject `MatDialog` service. Pass data via `MAT_DIALOG_DATA` token. Receive results via `afterClosed()`.
731
+
732
+ **`dialog.open()` Config:**
733
+ - `data`: any — passed to dialog component
734
+ - `width`, `maxWidth`, `height`: string
735
+ - `disableClose`: boolean (prevent backdrop click / Escape close)
736
+ - `autoFocus`: `'first-tabbable'` | `'dialog'` | `false`
737
+ - `panelClass`: string | string[] — add CSS classes to overlay
738
+
739
+ **Accessibility:** Focus is trapped inside the dialog. Escape closes by default. Dialog role is `dialog` with `aria-labelledby` pointing to title.
740
+
741
+ ```typescript
742
+ // Caller component
743
+ import { MatDialog } from '@angular/material/dialog';
744
+ import { ConfirmDialogComponent } from './confirm-dialog.component';
745
+
746
+ readonly #dialog = inject(MatDialog);
747
+
748
+ openConfirm() {
749
+ const ref = this.#dialog.open(ConfirmDialogComponent, {
750
+ width: '400px',
751
+ data: { message: 'Delete this item permanently?' }
752
+ });
753
+
754
+ ref.afterClosed().subscribe(confirmed => {
755
+ if (confirmed) this.delete();
756
+ });
757
+ }
758
+ ```
759
+
760
+ ```typescript
761
+ // Dialog component
762
+ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
763
+
764
+ @Component({
765
+ selector: 'app-confirm-dialog',
766
+ template: `
767
+ <h2 mat-dialog-title>Confirm</h2>
768
+ <mat-dialog-content>{{ data.message }}</mat-dialog-content>
769
+ <mat-dialog-actions align="end">
770
+ <button mat-stroked-button mat-dialog-close>Cancel</button>
771
+ <button mat-flat-button color="warn" [mat-dialog-close]="true">Delete</button>
772
+ </mat-dialog-actions>
773
+ `
774
+ })
775
+ export class ConfirmDialogComponent {
776
+ readonly data = inject<{ message: string }>(MAT_DIALOG_DATA);
777
+ }
778
+ ```
779
+
780
+ ---
781
+
782
+ ### `MatSnackBar`
783
+
784
+ Brief, non-disruptive notifications. Max 2 lines of text. Use action button for undo patterns.
785
+
786
+ **Duration guidance:**
787
+ - Informational: `3000ms`
788
+ - Success with undo: `5000ms`
789
+ - Errors with action: `10000ms` or `0` (manual dismiss)
790
+
791
+ **`snackBar.open()` Config:**
792
+ - `duration`: ms (0 = no auto-dismiss)
793
+ - `horizontalPosition`: `'start'` | `'center'` | `'end'` | `'left'` | `'right'`
794
+ - `verticalPosition`: `'top'` | `'bottom'`
795
+ - `panelClass`: string[]
796
+
797
+ ```typescript
798
+ readonly #snackBar = inject(MatSnackBar);
799
+
800
+ showSuccess(message: string) {
801
+ this.#snackBar.open(message, 'Dismiss', {
802
+ duration: 3000,
803
+ horizontalPosition: 'end',
804
+ verticalPosition: 'bottom',
805
+ panelClass: ['snack-success']
806
+ });
807
+ }
808
+
809
+ showUndo(message: string, undoFn: () => void) {
810
+ const ref = this.#snackBar.open(message, 'Undo', { duration: 5000 });
811
+ ref.onAction().subscribe(undoFn);
812
+ }
813
+ ```
814
+
815
+ ---
816
+
817
+ ### `mat-progress-bar`
818
+
819
+ Linear progress indicator. Use `indeterminate` for unknown duration operations; `determinate` when progress percentage is known.
820
+
821
+ **Key Inputs:**
822
+ - `mode`: `'determinate'` | `'indeterminate'` | `'buffer'` | `'query'`
823
+ - `[value]`: 0–100 (for determinate)
824
+ - `[bufferValue]`: 0–100 (for buffer mode)
825
+ - `color`: `'primary'` | `'accent'` | `'warn'`
826
+
827
+ **Accessibility:** Has `role="progressbar"` with `aria-valuenow` for determinate mode. Add `aria-label`.
828
+
829
+ ```html
830
+ <!-- Page-level loading bar (top of content) -->
831
+ <mat-progress-bar *ngIf="isLoading" mode="indeterminate" aria-label="Loading..." />
832
+
833
+ <!-- Upload progress -->
834
+ <mat-progress-bar mode="determinate" [value]="uploadProgress" aria-label="Upload progress" />
835
+ ```
836
+
837
+ ---
838
+
839
+ ### `mat-spinner` / `mat-progress-spinner`
840
+
841
+ Circular progress. `mat-spinner` is shorthand for `mode="indeterminate"`. Use inside buttons for async action feedback.
842
+
843
+ **Key Inputs:**
844
+ - `mode`: `'determinate'` | `'indeterminate'`
845
+ - `[value]`: 0–100
846
+ - `[diameter]`: number (px, default 100)
847
+ - `[strokeWidth]`: number
848
+
849
+ ```html
850
+ <!-- Button loading state -->
851
+ <button mat-flat-button color="primary" [disabled]="isSaving" (click)="save()">
852
+ <mat-spinner *ngIf="isSaving" diameter="20" strokeWidth="2" />
853
+ <span *ngIf="!isSaving">Save</span>
854
+ </button>
855
+
856
+ <!-- Full-page spinner -->
857
+ <div class="spinner-overlay" *ngIf="isLoading" role="status" aria-label="Loading">
858
+ <mat-spinner />
859
+ </div>
860
+ ```
861
+
862
+ ---
863
+
864
+ ### `mat-tooltip`
865
+
866
+ Hover/focus hints for supplementary information. Never place critical information only in tooltips (not accessible on touch devices).
867
+
868
+ **Key Inputs:**
869
+ - `[matTooltip]`: string — tooltip text
870
+ - `matTooltipPosition`: `'above'` | `'below'` | `'left'` | `'right'` | `'before'` | `'after'`
871
+ - `[matTooltipDisabled]`: boolean
872
+ - `matTooltipShowDelay`, `matTooltipHideDelay`: ms
873
+
874
+ **Accessibility:** Content is exposed via `aria-describedby`. Does not work for touch-only users — always provide alternative text.
875
+
876
+ ```html
877
+ <button mat-icon-button
878
+ aria-label="Delete item"
879
+ matTooltip="Delete this item permanently"
880
+ matTooltipPosition="above"
881
+ (click)="delete()">
882
+ <mat-icon>delete</mat-icon>
883
+ </button>
884
+
885
+ <!-- Disabled button with tooltip on wrapper -->
886
+ <span matTooltip="You don't have permission to edit">
887
+ <button mat-flat-button disabled>Edit</button>
888
+ </span>
889
+ ```
890
+
891
+ ---
892
+
893
+ ### `MatBottomSheet`
894
+
895
+ Mobile-first action sheets from the bottom of the screen. Use instead of dialogs on mobile for action menus.
896
+
897
+ ```typescript
898
+ readonly #bottomSheet = inject(MatBottomSheet);
899
+
900
+ openActions() {
901
+ const ref = this.#bottomSheet.open(ActionsSheetComponent, {
902
+ data: { item: this.selectedItem }
903
+ });
904
+ ref.afterDismissed().subscribe(action => {
905
+ if (action) this.handleAction(action);
906
+ });
907
+ }
908
+ ```
909
+
910
+ ---
911
+
912
+ ## Layout (CDK)
913
+
914
+ ### `CdkVirtualScrollViewport` — Virtual Scrolling
915
+
916
+ Renders only visible items. Essential for lists exceeding 100 items.
917
+
918
+ **Import:** `ScrollingModule` from `@angular/cdk/scrolling`
919
+
920
+ **Key Inputs:**
921
+ - `itemSize`: number (px, required for fixed-size strategy)
922
+ - `orientation`: `'vertical'` | `'horizontal'`
923
+ - `minBufferPx`, `maxBufferPx`: render buffer sizes
924
+
925
+ ```html
926
+ <cdk-virtual-scroll-viewport itemSize="72" class="list-viewport">
927
+ <mat-list-item *cdkVirtualFor="let item of items; trackBy: trackById">
928
+ <span matListItemTitle>{{ item.name }}</span>
929
+ </mat-list-item>
930
+ </cdk-virtual-scroll-viewport>
931
+ ```
932
+
933
+ ```scss
934
+ .list-viewport {
935
+ height: 400px; /* Must have fixed height */
936
+ width: 100%;
937
+ }
938
+ ```
939
+
940
+ ---
941
+
942
+ ### `CdkDragDrop` — Drag and Drop
943
+
944
+ Reorderable lists and kanban-style boards.
945
+
946
+ **Import:** `DragDropModule` from `@angular/cdk/drag-drop`
947
+
948
+ **Key Directives:**
949
+ - `cdkDropList`: container
950
+ - `cdkDrag`: draggable item
951
+ - `[cdkDropListConnectedTo]`: link multiple lists for cross-list drag
952
+
953
+ **Outputs:** `(cdkDropListDropped)`: `CdkDragDrop<T>`
954
+
955
+ ```html
956
+ <div cdkDropList class="task-list" (cdkDropListDropped)="drop($event)">
957
+ <div class="task-card" *ngFor="let task of tasks" cdkDrag>
958
+ <mat-icon cdkDragHandle>drag_indicator</mat-icon>
959
+ {{ task.title }}
960
+ <div *cdkDragPlaceholder class="drag-placeholder"></div>
961
+ </div>
962
+ </div>
963
+ ```
964
+
965
+ ```typescript
966
+ import { moveItemInArray, CdkDragDrop } from '@angular/cdk/drag-drop';
967
+
968
+ drop(event: CdkDragDrop<Task[]>) {
969
+ moveItemInArray(this.tasks, event.previousIndex, event.currentIndex);
970
+ }
971
+ ```
972
+
973
+ ---
974
+
975
+ ### `BreakpointObserver`
976
+
977
+ Responsive layout decisions in TypeScript. Prefer CSS media queries for styling; use `BreakpointObserver` only when layout changes require component logic.
978
+
979
+ **Import:** `LayoutModule` from `@angular/cdk/layout`
980
+
981
+ **Built-in breakpoints:** `Breakpoints.Handset`, `Breakpoints.Tablet`, `Breakpoints.Web`, `Breakpoints.HandsetPortrait`, etc.
982
+
983
+ ```typescript
984
+ import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
985
+ import { map, shareReplay } from 'rxjs/operators';
986
+
987
+ readonly #bp = inject(BreakpointObserver);
988
+
989
+ readonly isHandset$ = this.#bp
990
+ .observe(Breakpoints.Handset)
991
+ .pipe(
992
+ map(result => result.matches),
993
+ shareReplay(1)
994
+ );
995
+
996
+ readonly isMediumUp$ = this.#bp
997
+ .observe(['(min-width: 768px)'])
998
+ .pipe(map(r => r.matches));
999
+ ```
1000
+
1001
+ ---
1002
+
1003
+ ### `CdkPortal` — Portal / Teleport
1004
+
1005
+ Render a template or component in a different location in the DOM (e.g., inject content into a header from a child route).
1006
+
1007
+ **Import:** `PortalModule` from `@angular/cdk/portal`
1008
+
1009
+ ```typescript
1010
+ // Child component provides a portal
1011
+ @ViewChild('actionButtons') actionButtonsPortal!: TemplatePortal;
1012
+
1013
+ ngAfterViewInit() {
1014
+ this.headerService.setPortal(this.actionButtonsPortal);
1015
+ }
1016
+ ```
1017
+
1018
+ ```html
1019
+ <ng-template #actionButtons>
1020
+ <button mat-flat-button color="primary">Save</button>
1021
+ </ng-template>
1022
+ ```
1023
+
1024
+ ---
1025
+
1026
+ ## Icons
1027
+
1028
+ ### `mat-icon`
1029
+
1030
+ Display Material icons. Angular Material v15+ defaults to Material Symbols (variable font), which supports optical size, weight, fill, and grade axes.
1031
+
1032
+ **Setup in `index.html`:**
1033
+ ```html
1034
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet" />
1035
+ ```
1036
+
1037
+ **Key Inputs:**
1038
+ - `fontSet`: `'material-symbols-outlined'` | `'material-icons'`
1039
+ - `fontIcon`: icon name (alternative to text content)
1040
+ - `[inline]`: boolean (sizes to surrounding text)
1041
+ - `[color]`: `'primary'` | `'accent'` | `'warn'`
1042
+
1043
+ **Custom SVG icons via `MatIconRegistry`:**
1044
+ ```typescript
1045
+ // app.config.ts or component
1046
+ import { MatIconRegistry } from '@angular/material/icon';
1047
+ import { DomSanitizer } from '@angular/platform-browser';
1048
+
1049
+ const iconRegistry = inject(MatIconRegistry);
1050
+ const sanitizer = inject(DomSanitizer);
1051
+
1052
+ iconRegistry.addSvgIcon(
1053
+ 'custom-logo',
1054
+ sanitizer.bypassSecurityTrustResourceUrl('assets/icons/logo.svg')
1055
+ );
1056
+ ```
1057
+
1058
+ ```html
1059
+ <!-- Material Symbol -->
1060
+ <mat-icon fontSet="material-symbols-outlined">rocket_launch</mat-icon>
1061
+
1062
+ <!-- Custom SVG -->
1063
+ <mat-icon svgIcon="custom-logo" aria-hidden="true" />
1064
+
1065
+ <!-- Decorative (hide from screen readers) -->
1066
+ <mat-icon aria-hidden="true">star</mat-icon>
1067
+
1068
+ <!-- Meaningful (describe to screen readers) -->
1069
+ <mat-icon aria-label="Starred">star</mat-icon>
1070
+ ```
1071
+
1072
+ **Accessibility:** Set `aria-hidden="true"` on decorative icons. For meaningful standalone icons, add `aria-label`.
1073
+
1074
+ ---
1075
+
1076
+ ## Import Reference
1077
+
1078
+ ```typescript
1079
+ // Commonly needed imports (standalone component style)
1080
+ import { MatButtonModule } from '@angular/material/button';
1081
+ import { MatIconModule } from '@angular/material/icon';
1082
+ import { MatFormFieldModule } from '@angular/material/form-field';
1083
+ import { MatInputModule } from '@angular/material/input';
1084
+ import { MatSelectModule } from '@angular/material/select';
1085
+ import { MatAutocompleteModule } from '@angular/material/autocomplete';
1086
+ import { MatCheckboxModule } from '@angular/material/checkbox';
1087
+ import { MatRadioModule } from '@angular/material/radio';
1088
+ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
1089
+ import { MatDatepickerModule } from '@angular/material/datepicker';
1090
+ import { MatNativeDateModule } from '@angular/material/core';
1091
+ import { MatSliderModule } from '@angular/material/slider';
1092
+ import { MatChipsModule } from '@angular/material/chips';
1093
+ import { MatToolbarModule } from '@angular/material/toolbar';
1094
+ import { MatSidenavModule } from '@angular/material/sidenav';
1095
+ import { MatListModule } from '@angular/material/list';
1096
+ import { MatTabsModule } from '@angular/material/tabs';
1097
+ import { MatStepperModule } from '@angular/material/stepper';
1098
+ import { MatMenuModule } from '@angular/material/menu';
1099
+ import { MatTableModule } from '@angular/material/table';
1100
+ import { MatPaginatorModule } from '@angular/material/paginator';
1101
+ import { MatSortModule } from '@angular/material/sort';
1102
+ import { MatCardModule } from '@angular/material/card';
1103
+ import { MatExpansionModule } from '@angular/material/expansion';
1104
+ import { MatBadgeModule } from '@angular/material/badge';
1105
+ import { MatDividerModule } from '@angular/material/divider';
1106
+ import { MatDialogModule } from '@angular/material/dialog';
1107
+ import { MatSnackBarModule } from '@angular/material/snack-bar';
1108
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
1109
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
1110
+ import { MatTooltipModule } from '@angular/material/tooltip';
1111
+ import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
1112
+ import { ScrollingModule } from '@angular/cdk/scrolling';
1113
+ import { DragDropModule } from '@angular/cdk/drag-drop';
1114
+ import { LayoutModule } from '@angular/cdk/layout';
1115
+ import { PortalModule } from '@angular/cdk/portal';
1116
+ ```