ng-adflowtail 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,657 @@
1
+ # ng-adflowtail 🚀
2
+
3
+ A highly professional, developer-friendly, and lightweight Angular 22 component library built on top of **Tailwind CSS**, **Flowbite**, and modern **Angular Signals**.
4
+
5
+ `ng-adflowtail` is architected with a **secondary entry points** design (similar to `@angular/material`). This enables absolute tree-shaking, meaning consumers only bundle the precise widgets and logic they import, leading to ultra-slim production bundles.
6
+
7
+ ---
8
+
9
+ ## Key Features
10
+
11
+ - **Angular 22 Core**: Leverages the absolute latest APIs including Standalone Components, Signal-based Inputs/Outputs, Computeds, and Host Directives.
12
+ - **Advanced State Management**: Built natively around Signals to ensure highly performant and predictable change detection.
13
+ - **Signals-Based Forms**: Clean integration with modern Angular Signal Forms (`@angular/forms/signals`).
14
+ - **Dynamic CSS Custom Properties (Theming)**: Easily brand components in real-time by passing a single hex/rgb color string. The built-in directive dynamically computes complementary brand shades using modern CSS `color-mix()`.
15
+ - **Responsive & Accessible**: Fully styled with Tailwind CSS and Flowbite, implementing modern accessibility requirements and responsive layouts.
16
+ - **Full Tree-Shaking**: Secondary entry points keep your production bundles optimal.
17
+
18
+ ---
19
+
20
+ ## Table of Contents
21
+
22
+ 1. [Prerequisites & Installation](#prerequisites--installation)
23
+ 2. [Tailwind CSS Configuration](#tailwind-css-configuration)
24
+ 3. [Architecture & Imports](#architecture--scaffolding)
25
+ 4. [Branding & Custom Theming](#branding--custom-theming)
26
+ 5. [Component: AdMultiSelect](#component-admultiselect)
27
+ 6. [Component: AdDataTable](#component-addatatable)
28
+ 7. [Adding New Components (Library Developers)](#adding-new-components)
29
+ 8. [Build & Publish Commands](#build--publish)
30
+
31
+ ---
32
+
33
+ ## Prerequisites & Installation
34
+
35
+ ### 1. Install the NPM Package
36
+ Install `ng-adflowtail` and its required peers `flowbite` and `tslib`:
37
+
38
+ ```bash
39
+ npm install ng-adflowtail flowbite tslib
40
+ ```
41
+
42
+ ### 2. Install Tailwind CSS
43
+ Ensure your Angular project has Tailwind CSS set up. If not, follow the official [Tailwind CSS installation guide for Angular](https://tailwindcss.com/docs/guides/angular).
44
+
45
+ ---
46
+
47
+ ## Tailwind CSS Configuration
48
+
49
+ Because `ng-adflowtail` relies on Tailwind classes, you **must** configure Tailwind to scan the library's compiled templates inside `node_modules` so that styles are not purged in production builds.
50
+
51
+ ### For Tailwind CSS v4 (Modern CSS Configuration)
52
+ Add the `@source` directive in your main CSS file (e.g., `styles.css`):
53
+
54
+ ```css
55
+ @import "tailwindcss";
56
+
57
+ /* Add the source path to scan ng-adflowtail templates */
58
+ @source "../../node_modules/ng-adflowtail";
59
+ ```
60
+
61
+ ### For Tailwind CSS v3 (`tailwind.config.js`)
62
+ Add the `ng-adflowtail` path to your `content` array:
63
+
64
+ ```javascript
65
+ /** @type {import('tailwindcss').Config} */
66
+ module.exports = {
67
+ content: [
68
+ "./src/**/*.{html,ts}",
69
+ "./node_modules/ng-adflowtail/**/*.{js,mjs,html}" // Add this line
70
+ ],
71
+ theme: {
72
+ extend: {},
73
+ },
74
+ plugins: [
75
+ require('flowbite/plugin') // Enable Flowbite utility classes
76
+ ],
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Architecture & Scaffolding
83
+
84
+ To import components, use their specialized entry points. Do **not** import everything from `ng-adflowtail` directly.
85
+
86
+ ```typescript
87
+ // Core Directives & Shared Interfaces (Primary Entry Point)
88
+ import { AdPrimaryColorDirective, AdColumn } from 'ng-adflowtail';
89
+
90
+ // Individual Secondary Entry Points
91
+ import { AdMultiSelect } from 'ng-adflowtail/ad-multi-select';
92
+ import { AdDataTable } from 'ng-adflowtail/ad-data-table';
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Branding & Custom Theming
98
+
99
+ ### `AdPrimaryColorDirective` (`[adPrimaryColor]`)
100
+
101
+ Both `AdMultiSelect` and `AdDataTable` support dynamic theming via the `primaryColor` input. This input is automatically mapped on both components using Angular's robust **Host Directives** feature, delegating to the standalone `AdPrimaryColorDirective`.
102
+
103
+ This directive dynamically binds modern CSS Custom Properties (`--color-brand`, `--color-fg-brand`, etc.) to the host elements. Under the hood, it uses native CSS `color-mix()` to calculate brand states on-the-fly based on any valid CSS color input (hex, rgb, hsl, or web colors):
104
+
105
+ ```typescript
106
+ // Mapped custom properties on the host element style:
107
+ --color-brand: [primaryColor];
108
+ --color-brand-soft: color-mix(in srgb, [primaryColor] 10%, transparent);
109
+ --color-brand-softer: color-mix(in srgb, [primaryColor] 5%, transparent);
110
+ --color-brand-medium: color-mix(in srgb, [primaryColor] 20%, transparent);
111
+ --color-brand-strong: color-mix(in srgb, [primaryColor] 90%, black);
112
+ ```
113
+
114
+ #### Example: Dynamic Color Picker Implementation
115
+ You can bind a color input or a signal directly to the widgets:
116
+
117
+ ```html
118
+ <!-- Fully rebranded multi-select -->
119
+ <ad-multi-select
120
+ [formField]="userForm.permissions"
121
+ [options]="permissionOptions"
122
+ primaryColor="#0d9488"
123
+ />
124
+
125
+ <!-- Rebranded data table -->
126
+ <ad-data-table
127
+ [data]="users"
128
+ [config]="tableConfig"
129
+ primaryColor="#7c3aed"
130
+ />
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Component: AdMultiSelect
136
+
137
+ The `AdMultiSelect` component (`ng-adflowtail/ad-multi-select`) is a highly interactive, accessible dropdown multi-select. It is integrated natively with the new **Angular Signal-based Forms** (`@angular/forms/signals`), displaying selection tokens as elegant, dismissible chips.
138
+
139
+ ### Key Features
140
+ - **Signal Forms**: Directly binds to form fields via the `formField` input parameter.
141
+ - **Interactive Tokens**: Selected values render as clean chips with a close button.
142
+ - **Global Actions**: Includes fast-access "Select all" and "Clear" utilities.
143
+ - **Robust UX**: Includes click-outside detection and `Escape` keyboard key handling to dismiss the dropdown panel safely.
144
+ - **Validation-Driven**: Automatically applies red borders and error displays when the bound signal form is `touched` and `invalid`.
145
+
146
+ ---
147
+
148
+ ### Multi-Select API Spec
149
+
150
+ #### `@Input()` Bindings
151
+ | Input | Type | Default | Description |
152
+ | :--- | :--- | :--- | :--- |
153
+ | `formField` (Required) | `FieldTree<string[]>` | — | The signal forms field node to bind (e.g. `myForm.roles`). |
154
+ | `options` | `AdMultiSelectOption[]` | `[]` | List of items available in the dropdown selector. |
155
+ | `placeholder` | `string` | `'Select options'` | Label shown when no selection has been made yet. |
156
+ | `disabled` | `boolean` | `false` | Disables user interaction and prevents opening the menu. |
157
+ | `errorMessage` | `string` | `''` | Custom message text shown underneath the input on validation failure. |
158
+ | `primaryColor` | `string` | — | Overrides default branding colors with computed dynamic CSS colors. |
159
+
160
+ #### Model Interfaces
161
+
162
+ ```typescript
163
+ export interface AdMultiSelectOption {
164
+ label: string;
165
+ value: string;
166
+ }
167
+ ```
168
+
169
+ ---
170
+
171
+ ### Complete Multi-Select Example
172
+
173
+ #### Component TS (`app.component.ts`)
174
+ ```typescript
175
+ import { Component, signal } from '@angular/core';
176
+ import { form, required } from '@angular/forms/signals';
177
+ import { AdMultiSelect, AdMultiSelectOption } from 'ng-adflowtail/ad-multi-select';
178
+
179
+ interface PermissionForm {
180
+ permissions: string[];
181
+ }
182
+
183
+ @Component({
184
+ selector: 'app-permission-manager',
185
+ standalone: true,
186
+ imports: [AdMultiSelect],
187
+ templateUrl: './app.component.html'
188
+ })
189
+ export class AppPermissionManager {
190
+ // Brand color customization
191
+ protected readonly themeColor = signal<string>('#0d9488'); // Custom Teal
192
+
193
+ // Local state container
194
+ protected readonly formModel = signal<PermissionForm>({
195
+ permissions: ['read']
196
+ });
197
+
198
+ // Signal forms validation & controller
199
+ protected readonly userForm = form(this.formModel, (path) => {
200
+ required(path.permissions, { message: 'Please select at least one permission.' });
201
+ });
202
+
203
+ // Available options
204
+ protected readonly permissionOptions: AdMultiSelectOption[] = [
205
+ { label: 'Read Access', value: 'read' },
206
+ { label: 'Write Access', value: 'write' },
207
+ { label: 'Delete Actions', value: 'delete' },
208
+ { label: 'Super Admin', value: 'admin' }
209
+ ];
210
+
211
+ protected onSubmit(event: Event) {
212
+ event.preventDefault();
213
+ if (this.userForm.valid()) {
214
+ console.log('Submitting verified model state:', this.formModel());
215
+ } else {
216
+ this.userForm.markAsTouched();
217
+ console.warn('Form validation failed.');
218
+ }
219
+ }
220
+ }
221
+ ```
222
+
223
+ #### Component HTML Template (`app.component.html`)
224
+ ```html
225
+ <div class="max-w-md p-6 bg-white dark:bg-gray-800 rounded-xl shadow border border-gray-200 dark:border-gray-700">
226
+ <form (submit)="onSubmit($event)" class="space-y-4">
227
+ <div>
228
+ <label class="block mb-2 text-sm font-semibold text-gray-900 dark:text-white">
229
+ Role Permissions
230
+ </label>
231
+
232
+ <!-- Multi Select Binding -->
233
+ <ad-multi-select
234
+ [formField]="userForm.permissions"
235
+ [options]="permissionOptions"
236
+ [primaryColor]="themeColor()"
237
+ placeholder="Select key permissions"
238
+ errorMessage="At least one permission is mandatory"
239
+ />
240
+ </div>
241
+
242
+ <!-- Active State Inspector -->
243
+ <div class="p-3 bg-gray-50 dark:bg-gray-900/50 rounded-lg text-xs">
244
+ <p class="font-semibold text-gray-500">Live Form Value:</p>
245
+ <code class="text-indigo-600 dark:text-indigo-400">{{ formModel() | json }}</code>
246
+ </div>
247
+
248
+ <button type="submit" class="w-full text-white bg-blue-600 hover:bg-blue-700 font-medium rounded-lg text-sm px-5 py-2.5">
249
+ Save Rules
250
+ </button>
251
+ </form>
252
+ </div>
253
+ ```
254
+
255
+ ---
256
+
257
+ ## Component: AdDataTable
258
+
259
+ The `AdDataTable` component (`ng-adflowtail/ad-data-table`) is a high-performance, feature-rich responsive table widget that simplifies handling large datasets.
260
+
261
+ ### Key Features
262
+ - **Dynamic Columns**: Configurable fields with automated rendering based on data types (`text`, `number`, `date`, `currency` pre-formatted for SAR, `status`, `action`, `template`).
263
+ - **Flexible Row Actions**: Built-in row buttons (EDIT, DELETE, VIEW) with custom text, colors, visibility conditions, and disable state callbacks per row.
264
+ - **Built-in Global Search**: Instant master text filter across row records.
265
+ - **Advanced Column Filters**: Supports field-by-field searching with specific widgets (`text`, `select`, `multiSelect`, `date`, `dateRange`, `number`, `autocomplete`).
266
+ - **Paging Controls**: Custom pagination bar showing start/end ranges, previous/next controls, and customizable page size selections.
267
+
268
+ ---
269
+
270
+ ### Data Table API Spec
271
+
272
+ #### `@Input()` Bindings
273
+ | Input | Type | Default | Description |
274
+ | :--- | :--- | :--- | :--- |
275
+ | `title` | `string` | `''` | Headline shown above the table card. |
276
+ | `data` | `any[]` | `[]` | Array of data records to display. |
277
+ | `tableStyle` | `string` | — | Custom inline styling or CSS classes applied directly to the outer wrapper. |
278
+ | `config` | `TableConfig` | See defaults below | Master schema and controls governing column capabilities and pagination. |
279
+ | `primaryColor` | `string` | — | Overrides default branding colors with computed dynamic CSS colors. |
280
+
281
+ **Default TableConfig Object:**
282
+ ```typescript
283
+ {
284
+ columns: [],
285
+ itemsPerPage: 10,
286
+ showPagination: true,
287
+ showPageSize: true,
288
+ pageSizeOptions: [5, 10, 25, 50],
289
+ showSearch: true,
290
+ searchPlaceholder: 'Search...',
291
+ showActions: false,
292
+ showColumnSearch: false
293
+ }
294
+ ```
295
+
296
+ #### `@Output()` Events
297
+ | Event | Payload Type | Description |
298
+ | :--- | :--- | :--- |
299
+ | `pageChange` | `number` | Emits the new page number selected by the user. |
300
+ | `pageSizeChange` | `number` | Emits the new page size count chosen in the dropdown. |
301
+ | `rowClick` | `any` | Emits the raw record object when a row is clicked. |
302
+ | `actionClick` | `{ action: DataTableButtonAction; item: any }` | Triggered when a predefined action button in a row is clicked. |
303
+ | `searchChange` | `string` | Emits the global search string entered by the user. |
304
+ | `columnSearchChange` | `ColumnSearchEvent` | Triggered when any column-specific search input values are modified. |
305
+
306
+ ---
307
+
308
+ ### Data Table Types & Interfaces
309
+
310
+ ```typescript
311
+ export type DataTableButtonAction = 'EDIT' | 'DELETE' | 'VIEW';
312
+
313
+ export interface TableColumn {
314
+ field: string;
315
+ header: string;
316
+ type?: 'text' | 'number' | 'date' | 'currency' | 'status' | 'action' | 'template';
317
+ format?: string;
318
+ actionTemplate?: (item: any) => string;
319
+ statusColors?: { [key: string]: string };
320
+ searchable?: boolean;
321
+ searchPlaceholder?: string;
322
+ searchType?: 'text' | 'select' | 'multiSelect' | 'date' | 'dateRange' | 'number' | 'autocomplete';
323
+ searchOptions?: Array<{ value: any; label: string }>;
324
+ multiSelectConfig?: {
325
+ maxItems?: number;
326
+ showSelectAll?: boolean;
327
+ };
328
+ autocompleteConfig?: {
329
+ apiUrl?: string;
330
+ debounceTime?: number;
331
+ minChars?: number;
332
+ valueField?: string;
333
+ labelField?: string;
334
+ customSearchFn?: (searchTerm: string) => Promise<Array<{ value: any; label: string }>>;
335
+ };
336
+ actions?: TableAction[];
337
+ }
338
+
339
+ export interface TableAction {
340
+ id: DataTableButtonAction;
341
+ label: string;
342
+ icon?: string;
343
+ color?: 'primary' | 'success' | 'danger' | 'warning' | 'info' | 'secondary';
344
+ className?: string;
345
+ visible?: (item: any) => boolean;
346
+ disabled?: (item: any) => boolean;
347
+ tooltip?: string;
348
+ }
349
+
350
+ export interface TableConfig {
351
+ columns: TableColumn[];
352
+ totalCount?: number;
353
+ currentPage?: number;
354
+ pageSize?: number;
355
+ itemsPerPage?: number;
356
+ showPagination?: boolean;
357
+ showPageSize?: boolean;
358
+ pageSizeOptions?: number[];
359
+ showSearch?: boolean;
360
+ searchPlaceholder?: string;
361
+ showActions?: boolean;
362
+ showColumnSearch?: boolean;
363
+ }
364
+
365
+ export interface ColumnSearchEvent {
366
+ column: string;
367
+ value: any;
368
+ field: string;
369
+ searchType: string;
370
+ }
371
+ ```
372
+
373
+ ---
374
+
375
+ ### Complete Data Table Example
376
+
377
+ #### Component TS (`app.component.ts`)
378
+ ```typescript
379
+ import { Component, computed, OnInit, signal } from '@angular/core';
380
+ import { CommonModule } from '@angular/common';
381
+ import { AdDataTable, TableConfig, ColumnSearchEvent } from 'ng-adflowtail/ad-data-table';
382
+
383
+ interface Employee {
384
+ id: number;
385
+ name: string;
386
+ email: string;
387
+ department: string;
388
+ salary: number;
389
+ hireDate: string;
390
+ status: 'Active' | 'On Leave' | 'Terminated';
391
+ }
392
+
393
+ @Component({
394
+ selector: 'app-dashboard-table',
395
+ standalone: true,
396
+ imports: [CommonModule, AdDataTable],
397
+ templateUrl: './app.component.html'
398
+ })
399
+ export class AppDashboardTable implements OnInit {
400
+ protected readonly brandColor = signal<string>('#7c3aed'); // Violet theme
401
+
402
+ // Datastore
403
+ protected readonly employees = signal<Employee[]>([]);
404
+
405
+ // Filtering, Search and Pagination states
406
+ protected readonly currentPage = signal<number>(1);
407
+ protected readonly pageSize = signal<number>(5);
408
+ protected readonly globalSearch = signal<string>('');
409
+ protected readonly columnFilters = signal<Record<string, string>>({});
410
+
411
+ ngOnInit() {
412
+ // Bootstrap dummy data
413
+ this.employees.set([
414
+ { id: 101, name: 'Ahmad Al-Harbi', email: 'ahmad@company.com', department: 'Engineering', salary: 18500, hireDate: '2023-04-12', status: 'Active' },
415
+ { id: 102, name: 'Sarah Connor', email: 'sarah@company.com', department: 'Operations', salary: 14200, hireDate: '2021-08-19', status: 'Active' },
416
+ { id: 103, name: 'John Doe', email: 'john.d@company.com', department: 'Engineering', salary: 12000, hireDate: '2025-01-05', status: 'On Leave' },
417
+ { id: 104, name: 'Lina Gamil', email: 'lina@company.com', department: 'Marketing', salary: 9800, hireDate: '2022-11-01', status: 'Active' },
418
+ { id: 105, name: 'Michael Scott', email: 'michael@company.com', department: 'Sales', salary: 11500, hireDate: '2019-03-15', status: 'Terminated' }
419
+ ]);
420
+ }
421
+
422
+ // Reactive computed: filters records
423
+ protected readonly filteredEmployees = computed(() => {
424
+ let list = this.employees();
425
+ const search = this.globalSearch().toLowerCase();
426
+ const colFilters = this.columnFilters();
427
+
428
+ // 1. Process Global Master Search
429
+ if (search) {
430
+ list = list.filter(item =>
431
+ item.name.toLowerCase().includes(search) ||
432
+ item.email.toLowerCase().includes(search) ||
433
+ item.department.toLowerCase().includes(search)
434
+ );
435
+ }
436
+
437
+ // 2. Process Specific Column Searches
438
+ for (const [key, value] of Object.entries(colFilters)) {
439
+ if (value) {
440
+ list = list.filter(item => {
441
+ const itemVal = (item as any)[key]?.toString().toLowerCase();
442
+ return itemVal?.includes(value.toLowerCase());
443
+ });
444
+ }
445
+ }
446
+
447
+ return list;
448
+ });
449
+
450
+ // Reactive computed: extracts sliced pagination view
451
+ protected readonly paginatedEmployees = computed(() => {
452
+ const list = this.filteredEmployees();
453
+ const start = (this.currentPage() - 1) * this.pageSize();
454
+ const end = start + this.pageSize();
455
+ return list.slice(start, end);
456
+ });
457
+
458
+ protected readonly totalCount = computed(() => this.filteredEmployees().length);
459
+
460
+ // Dynamic Reactive Table Config
461
+ protected readonly tableConfig = computed<TableConfig>(() => ({
462
+ columns: [
463
+ { field: 'id', header: 'ID', type: 'number' },
464
+ { field: 'name', header: 'Full Name', type: 'text', searchable: true },
465
+ { field: 'email', header: 'Email Address', type: 'text', searchable: true },
466
+ {
467
+ field: 'department',
468
+ header: 'Department',
469
+ type: 'text',
470
+ searchable: true,
471
+ searchType: 'select',
472
+ searchOptions: [
473
+ { value: 'Engineering', label: 'Engineering' },
474
+ { value: 'Operations', label: 'Operations' },
475
+ { value: 'Marketing', label: 'Marketing' },
476
+ { value: 'Sales', label: 'Sales' }
477
+ ]
478
+ },
479
+ { field: 'salary', header: 'Base Salary', type: 'currency' },
480
+ { field: 'hireDate', header: 'Hire Date', type: 'date' },
481
+ {
482
+ field: 'status',
483
+ header: 'Status',
484
+ type: 'status',
485
+ searchable: true,
486
+ searchType: 'select',
487
+ searchOptions: [
488
+ { value: 'Active', label: 'Active' },
489
+ { value: 'On Leave', label: 'On Leave' },
490
+ { value: 'Terminated', label: 'Terminated' }
491
+ ],
492
+ statusColors: {
493
+ 'Active': 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400',
494
+ 'On Leave': 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400',
495
+ 'Terminated': 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400'
496
+ }
497
+ },
498
+ {
499
+ field: 'actions',
500
+ header: 'Actions',
501
+ type: 'action',
502
+ actions: [
503
+ { id: 'VIEW', label: 'View Profile', color: 'primary' },
504
+ {
505
+ id: 'DELETE',
506
+ label: 'Remove',
507
+ color: 'danger',
508
+ disabled: (row) => row.status === 'Terminated' // Disable delete button for terminated staff
509
+ }
510
+ ]
511
+ }
512
+ ],
513
+ showPagination: true,
514
+ showPageSize: true,
515
+ pageSizeOptions: [5, 10, 20],
516
+ showSearch: true,
517
+ searchPlaceholder: 'Search directory...',
518
+ showActions: true,
519
+ showColumnSearch: true,
520
+ totalCount: this.totalCount(),
521
+ currentPage: this.currentPage(),
522
+ pageSize: this.pageSize()
523
+ }));
524
+
525
+ // Handle Reactive Handlers
526
+ protected onPageChange(page: number) {
527
+ this.currentPage.set(page);
528
+ }
529
+
530
+ protected onPageSizeChange(size: number) {
531
+ this.pageSize.set(size);
532
+ this.currentPage.set(1); // Reset to first page
533
+ }
534
+
535
+ protected onGlobalSearch(searchTerm: string) {
536
+ this.globalSearch.set(searchTerm);
537
+ this.currentPage.set(1);
538
+ }
539
+
540
+ protected onColumnSearch(event: ColumnSearchEvent) {
541
+ this.columnFilters.update(filters => ({
542
+ ...filters,
543
+ [event.field]: event.value
544
+ }));
545
+ this.currentPage.set(1);
546
+ }
547
+
548
+ protected onActionTriggered(event: { action: string; item: any }) {
549
+ console.log(`Action triggered: [${event.action}] on record ID:`, event.item.id);
550
+ }
551
+
552
+ protected onRowSelected(row: any) {
553
+ console.log('Row record clicked:', row);
554
+ }
555
+ }
556
+ ```
557
+
558
+ #### Component HTML Template (`app.component.html`)
559
+ ```html
560
+ <div class="container mx-auto p-4">
561
+ <ad-data-table
562
+ title="Staff Directory"
563
+ [data]="paginatedEmployees()"
564
+ [config]="tableConfig()"
565
+ [primaryColor]="brandColor()"
566
+ (pageChange)="onPageChange($event)"
567
+ (pageSizeChange)="onPageSizeChange($event)"
568
+ (searchChange)="onGlobalSearch($event)"
569
+ (columnSearchChange)="onColumnSearch($event)"
570
+ (actionClick)="onActionTriggered($event)"
571
+ (rowClick)="onRowSelected($event)"
572
+ />
573
+ </div>
574
+ ```
575
+
576
+ ---
577
+
578
+ ## Adding New Components
579
+
580
+ If you are a library developer working on the `ng-adflowtail` monorepo workspace, you can easily add new components using ng-packagr's standard secondary entry points system.
581
+
582
+ ### Steps to scaffold a new widget `ad-badge`
583
+
584
+ #### 1. Setup Folder Structure
585
+ Create a new folder alongside the main `src` directory in `projects/ng-adflowtail/`:
586
+
587
+ ```
588
+ projects/ng-adflowtail/
589
+ ├── src/ # primary entry point
590
+ ├── ad-multi-select/ # secondary entry point 1
591
+ ├── ad-data-table/ # secondary entry point 2
592
+ └── ad-badge/ # <--- Your new secondary entry point
593
+ ├── ng-package.json
594
+ └── src/
595
+ ├── public-api.ts
596
+ └── lib/
597
+ ├── ad-badge.ts
598
+ ├── ad-badge.html
599
+ └── ad-badge.css
600
+ ```
601
+
602
+ #### 2. Create `projects/ng-adflowtail/ad-badge/ng-package.json`
603
+ Define the secondary packaging schema pointing back to the library:
604
+
605
+ ```json
606
+ {
607
+ "lib": {
608
+ "entryFile": "src/public-api.ts"
609
+ }
610
+ }
611
+ ```
612
+
613
+ #### 3. Create `projects/ng-adflowtail/ad-badge/src/public-api.ts`
614
+ Export your exports correctly:
615
+
616
+ ```typescript
617
+ export * from './lib/ad-badge';
618
+ ```
619
+
620
+ #### 4. Configure compiler paths mapping in Root `tsconfig.json`
621
+ Allow main apps in the monorepo to resolve imports locally during development:
622
+
623
+ ```json
624
+ "paths": {
625
+ "ng-adflowtail": ["./projects/ng-adflowtail/src/public-api.ts"],
626
+ "ng-adflowtail/ad-data-table": ["./projects/ng-adflowtail/ad-data-table/src/public-api.ts"],
627
+ "ng-adflowtail/ad-multi-select": ["./projects/ng-adflowtail/ad-multi-select/src/public-api.ts"],
628
+ "ng-adflowtail/ad-badge": ["./projects/ng-adflowtail/ad-badge/src/public-api.ts"] // Add this
629
+ }
630
+ ```
631
+
632
+ ---
633
+
634
+ ## Build & Publish
635
+
636
+ ### Compile the entire package
637
+ Use the Angular CLI to run a production build. Under the hood, `ng-packagr` compiles all the secondary entry points in sequence:
638
+
639
+ ```bash
640
+ ng build ng-adflowtail
641
+ ```
642
+
643
+ The compiled distributable files are stored under `/dist/ng-adflowtail`.
644
+
645
+ ### Publish to NPM
646
+ Navigate to the build directory and run the publish command:
647
+
648
+ ```bash
649
+ cd dist/ng-adflowtail
650
+ npm publish --access public
651
+ ```
652
+
653
+ ---
654
+
655
+ ## License
656
+
657
+ MIT © Ahmad Workspace. Fully custom-crafted components built for Angular 22 and styled with Tailwind CSS & Flowbite.