shirkasoft-ui-components 1.0.22 → 1.0.24

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,685 @@
1
+ # Shirkasoft UI Components
2
+
3
+ <p align="center">
4
+ <a href="https://github.com/Ansem13/shirkasoft-ui-components/releases"><img src="https://img.shields.io/npm/v/shirkasoft-ui-components?style=flat&logo=npm&label=version" alt="npm version" /></a>
5
+ <a href="https://github.com/Ansem13/shirkasoft-ui-components/issues"><img src="https://img.shields.io/github/issues/Ansem13/shirkasoft-ui-components?style=flat&logo=github" alt="GitHub issues" /></a>
6
+ <a href="https://github.com/Ansem13/shirkasoft-ui-components"><img src="https://img.shields.io/badge/source-github-181717?style=flat&logo=github" alt="GitHub source" /></a>
7
+ <a href="https://ansem13.github.io/shirkasoft-ui-components/"><img src="https://img.shields.io/badge/showcase-online-10b981?style=flat&logo=githubpages" alt="Showcase" /></a>
8
+ </p>
9
+
10
+ <p align="center">
11
+ Created by <a href="https://shirkasoft.com" target="_blank"><strong>Shirkasoft</strong></a>
12
+ </p>
13
+
14
+ Angular 21+ UI components library built with **Tailwind CSS**, **standalone components**, **Angular signals**, and **light/dark theme** support via CSS custom properties.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pnpm add shirkasoft-ui-components
20
+ # or
21
+ npm install shirkasoft-ui-components
22
+ ```
23
+
24
+ ### Peer Dependencies
25
+
26
+ | Package | Minimum version |
27
+ |---|---|
28
+ | @angular/common | ^21.2.0 |
29
+ | @angular/core | ^21.2.0 |
30
+ | @angular/forms | ^21.2.0 |
31
+ | rxjs | ^7.8.0 |
32
+ | @jsverse/transloco | >= 8.0.0 |
33
+ | @lucide/angular | ^1.17.0 |
34
+ | chart.js | ^4.4.0 |
35
+
36
+ ## Theme Setup
37
+
38
+ Import the theme in your global `styles.css`:
39
+
40
+ ```css
41
+ @import '@shirkasoft/ui-components/theme.css';
42
+ ```
43
+
44
+ The theme is based on `--shk-*` CSS variables. To customize colors, override the variables in `:root` (light mode) and `.dark` (dark mode):
45
+
46
+ ```css
47
+ :root {
48
+ --shk-primary-500: #3b82f6;
49
+ --shk-primary-600: #2563eb;
50
+ /* ... */
51
+ }
52
+ ```
53
+
54
+ ### Dark mode
55
+
56
+ Add the `dark` class to the `<html>` element to enable dark mode.
57
+
58
+ ```typescript
59
+ document.documentElement.classList.toggle('dark');
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Components
65
+
66
+ | # | Component | Selector | ControlValueAccessor | Associated service |
67
+ |---|---|---|---|---|
68
+ | 1 | TextField | `shk-text-field` | ✅ | — |
69
+ | 2 | Toggle | `shk-toggle` | ✅ | — |
70
+ | 3 | Select | `shk-select` | ✅ | — |
71
+ | 4 | DatePicker | `shk-date-picker` | ✅ | — |
72
+ | 5 | PriceInput | `shk-price-input` | — | — |
73
+ | 6 | FileUpload | `shk-file-upload` | — | — |
74
+ | 7 | Table | `shk-table` | — | — |
75
+ | 8 | Modal | `shk-modal` | — | `ModalService` |
76
+ | 9 | Notification | `shk-notification` | — | `NotificationService` |
77
+ | 10 | ConfirmDialog | `shk-confirm-dialog` | — | `ConfirmDialogService` |
78
+ | 11 | Chart | `shk-chart` | — | — |
79
+ | 12 | Tooltip | `shk-tooltip` | — | — |
80
+
81
+ ---
82
+
83
+ ### TextField (`shk-text-field`)
84
+
85
+ Text input with support for input, textarea, password, search, credit card formatting, and error validation. Implements `ControlValueAccessor`.
86
+
87
+ ```html
88
+ <shk-text-field
89
+ [label]="'Name'"
90
+ [control]="myControl"
91
+ [type]="'text'"
92
+ [placeholder]="'Enter your name'"
93
+ [disabled]="false"
94
+ [errorMessages]="{ required: 'This field is required' }"
95
+ [isTextArea]="false"
96
+ [searchMode]="false"
97
+ (searchButtonClick)="onSearch()"
98
+ />
99
+ ```
100
+
101
+ | Input | Type | Default |
102
+ |---|---|---|
103
+ | `id` | `string` | `''` |
104
+ | `label` | `string` | `''` |
105
+ | `type` | `string` | `'text'` |
106
+ | `placeholder` | `string` | `''` |
107
+ | `disabled` | `boolean` | `false` |
108
+ | `control` | `FormControl` | `undefined` |
109
+ | `formGroup` | `FormGroup` | `undefined` |
110
+ | `errorMessage` | `string` | `''` |
111
+ | `errorMessages` | `{ [key: string]: string }` | `{}` |
112
+ | `isTextArea` | `boolean` | `false` |
113
+ | `textAreaHeight` | `string` | `'h-28'` |
114
+ | `searchMode` | `boolean` | `false` |
115
+ | `searchButtonClick` | `output<void>` | — |
116
+ | `numbersOnly` | `boolean` | `false` |
117
+ | `allowDecimals` | `boolean` | `false` |
118
+ | `maxLength` | `number` | `undefined` |
119
+ | `formatCard` | `boolean` | `false` |
120
+ | `preventNegative` | `boolean` | `false` |
121
+ | `displayValue` | `string` | `''` |
122
+ | `autocomplete` | `string` | `'new-password'` |
123
+
124
+ > Also exposes `inputElement` and `textareaElement` as `viewChild`.
125
+
126
+ ---
127
+
128
+ ### Toggle (`shk-toggle`)
129
+
130
+ Switch or checkbox. Implements `ControlValueAccessor`.
131
+
132
+ ```html
133
+ <shk-toggle
134
+ [label]="'Enable notifications'"
135
+ [mode]="'toggle'"
136
+ [(checked)]="isChecked"
137
+ (checkedChange)="onChange($event)"
138
+ />
139
+ ```
140
+
141
+ | Input | Type | Default |
142
+ |---|---|---|
143
+ | `label` | `string` | `''` |
144
+ | `mode` | `'toggle' \| 'checkbox'` | `'toggle'` |
145
+ | `checked` | `boolean` | `undefined` |
146
+
147
+ | Output | Type |
148
+ |---|---|
149
+ | `checkedChange` | `boolean` |
150
+
151
+ ---
152
+
153
+ ### Select (`shk-select`)
154
+
155
+ Searchable dropdown with pagination, multiple selection, custom options, and validation. Implements `ControlValueAccessor`.
156
+
157
+ ```html
158
+ <shk-select
159
+ [options]="options"
160
+ [label]="'Country'"
161
+ [control]="countryControl"
162
+ [multiple]="false"
163
+ [isSearchable]="true"
164
+ [usePagination]="true"
165
+ [itemsPerPage]="10"
166
+ [allowCustomEntries]="false"
167
+ [placeholder]="'Select...'"
168
+ [isLoading]="false"
169
+ (selectionChange)="onSelect($event)"
170
+ (search)="onSearch($event)"
171
+ />
172
+ ```
173
+
174
+ ```typescript
175
+ interface SelectOption {
176
+ label: string;
177
+ value: any;
178
+ custom?: boolean;
179
+ }
180
+ ```
181
+
182
+ | Input | Type | Default |
183
+ |---|---|---|
184
+ | `options` | `SelectOption[]` | `[]` |
185
+ | `label` | `string` | `undefined` |
186
+ | `placeholder` | `string` | `'Select...'` |
187
+ | `disabled` | `boolean` | `false` |
188
+ | `control` | `FormControl` | `undefined` |
189
+ | `formGroup` | `FormGroup` | `undefined` |
190
+ | `multiple` | `boolean` | `false` |
191
+ | `isSearchable` | `boolean` | `false` |
192
+ | `usePagination` | `boolean` | `false` |
193
+ | `itemsPerPage` | `number` | `10` |
194
+ | `allowCustomEntries` | `boolean` | `false` |
195
+ | `isLoading` | `boolean` | `false` |
196
+ | `isAllDataLoaded` | `boolean` | `false` |
197
+ | `preserveSearchOnLoad` | `boolean` | `false` |
198
+ | `dropdownUpward` | `boolean` | `false` |
199
+ | `showEmptyOption` | `boolean` | `true` |
200
+ | `emptyMessageKey` | `string` | `'common.no_records'` |
201
+ | `validCombinations` | `string[][]` | `undefined` |
202
+ | `errorMessages` | `{ [key: string]: string }` | `{ required: 'This field is required' }` |
203
+
204
+ | Output | Type |
205
+ |---|---|
206
+ | `selectionChange` | `any` |
207
+ | `search` | `string` |
208
+
209
+ ---
210
+
211
+ ### DatePicker (`shk-date-picker`)
212
+
213
+ Date, month, or year picker. Implements `ControlValueAccessor`. Supports `'date'`, `'month'`, `'year'` views.
214
+
215
+ ```html
216
+ <shk-date-picker
217
+ [label]="'Start date'"
218
+ [control]="dateControl"
219
+ [view]="'date'"
220
+ [locale]="'es'"
221
+ [minValue]="'2024-01-01'"
222
+ [maxValue]="'2026-12-31'"
223
+ />
224
+ ```
225
+
226
+ | Input | Type | Default |
227
+ |---|---|---|
228
+ | `id` | `string` | `''` |
229
+ | `label` | `string` | `''` |
230
+ | `placeholder` | `string` | `''` |
231
+ | `view` | `'date' \| 'month' \| 'year'` | `'date'` |
232
+ | `control` | `FormControl` | `undefined` |
233
+ | `formGroup` | `FormGroup` | `undefined` |
234
+ | `disabled` | `boolean` | `false` |
235
+ | `errorMessage` | `string` | `''` |
236
+ | `errorMessages` | `{ [key: string]: string }` | `{}` |
237
+ | `minValue` | `string` | `''` |
238
+ | `maxValue` | `string` | `''` |
239
+ | `locale` | `string` | `'es'` |
240
+
241
+ **Return value** depends on `view`:
242
+ - `'date'` → `'YYYY-MM-DD'`
243
+ - `'month'` → `'YYYY-MM'`
244
+ - `'year'` → `'YYYY'`
245
+
246
+ ---
247
+
248
+ ### PriceInput (`shk-price-input`)
249
+
250
+ Numeric input with toggle between amount (`$`) and percentage (`%`). Uses Angular `model()` for two-way binding.
251
+
252
+ ```html
253
+ <shk-price-input
254
+ [(value)]="price"
255
+ [(isPercentage)]="isPercent"
256
+ [label]="'Price'"
257
+ [min]="0"
258
+ [max]="10000"
259
+ [required]="true"
260
+ [showError]="hasError"
261
+ [errorMessage]="'Invalid value'"
262
+ />
263
+ ```
264
+
265
+ | Input | Type | Default |
266
+ |---|---|---|
267
+ | `value` | `model<number>` | **required** |
268
+ | `isPercentage` | `model<boolean>` | `false` |
269
+ | `label` | `string` | `''` |
270
+ | `inputId` | `string` | `''` |
271
+ | `required` | `boolean` | `false` |
272
+ | `disabled` | `boolean` | `false` |
273
+ | `min` | `number` | `0` |
274
+ | `max` | `number` | `Infinity` |
275
+ | `showError` | `boolean` | `false` |
276
+ | `errorMessage` | `string` | `''` |
277
+
278
+ ---
279
+
280
+ ### FileUpload (`shk-file-upload`)
281
+
282
+ File upload with drag & drop, image preview, file type and size validation.
283
+
284
+ ```html
285
+ <shk-file-upload
286
+ [label]="'Upload your photo'"
287
+ [accept]="'image/*'"
288
+ [maxFileSize]="5 * 1024 * 1024"
289
+ [maxFiles]="3"
290
+ [multiple]="true"
291
+ (fileSelected)="onFilesSelected($event)"
292
+ (fileRemoved)="onFileRemoved()"
293
+ (fileError)="onFileError($event)"
294
+ />
295
+ ```
296
+
297
+ ```typescript
298
+ interface FileUploadError {
299
+ type: 'size' | 'type';
300
+ message: string;
301
+ file: File;
302
+ }
303
+ ```
304
+
305
+ | Input | Type | Default |
306
+ |---|---|---|
307
+ | `label` | `string` | `'Upload file'` |
308
+ | `accept` | `string` | `'image/*'` |
309
+ | `maxFileSize` | `number` | `2 * 1024 * 1024` (2MB) |
310
+ | `maxFiles` | `number` | `0` (unlimited) |
311
+ | `multiple` | `boolean` | `false` |
312
+ | `fileUploadText` | `string` | `'Select file'` |
313
+ | `changeFilesText` | `string` | `'Change files'` |
314
+ | `fileRecommendation` | `string` | `''` |
315
+
316
+ | Output | Type |
317
+ |---|---|
318
+ | `fileSelected` | `File[]` |
319
+ | `fileRemoved` | `void` |
320
+ | `fileError` | `FileUploadError` |
321
+ | `fileWarning` | `FileUploadError` |
322
+
323
+ ---
324
+
325
+ ### Table (`shk-table`)
326
+
327
+ Data table with sorting, column filters, global search, pagination, row actions, and header actions. Supports client and server-side mode.
328
+
329
+ ```html
330
+ <shk-table
331
+ [data]="users"
332
+ [columns]="columns"
333
+ [loading]="isLoading"
334
+ [serverSide]="true"
335
+ [totalRecords]="totalUsers"
336
+ [rowActions]="rowActions"
337
+ [headerActions]="headerActions"
338
+ [rowsPerPage]="25"
339
+ [showSearch]="true"
340
+ (pageChange)="onPageChange($event)"
341
+ (searchChange)="onSearch($event)"
342
+ (refresh)="loadData()"
343
+ />
344
+ ```
345
+
346
+ ```typescript
347
+ interface Column {
348
+ field: string;
349
+ header: string;
350
+ sortable?: boolean;
351
+ filter?: boolean;
352
+ filterPlaceholder?: string;
353
+ width?: string;
354
+ defaultSort?: true;
355
+ filterType?: 'text' | 'exact' | 'select';
356
+ filterOptions?: { label: string; value: any }[];
357
+ template?: 'text' | 'tag';
358
+ format?: (row: any) => string;
359
+ tagValue?: (row: any) => string;
360
+ tagSeverity?: (row: any) => 'success' | 'danger' | 'warn' | 'info' | undefined;
361
+ tagStyle?: (row: any) => { [key: string]: string } | undefined;
362
+ }
363
+
364
+ interface TableAction {
365
+ label: string;
366
+ icon: string;
367
+ onClick: () => void;
368
+ class?: string;
369
+ isVisible?: () => boolean;
370
+ isDisabled?: () => boolean;
371
+ }
372
+
373
+ interface RowAction {
374
+ label: string | ((data: any) => string);
375
+ icon: string | ((data: any) => string);
376
+ onClick: (rowData: any) => void;
377
+ class?: string | ((data: any) => string);
378
+ isVisible?: (rowData: any) => boolean;
379
+ isDisabled?: (rowData: any) => boolean;
380
+ }
381
+
382
+ interface PageChangeEvent {
383
+ first: number;
384
+ rows: number;
385
+ page: number;
386
+ pageCount: number;
387
+ }
388
+
389
+ interface FilterChangeEvent {
390
+ filters: { [key: string]: any };
391
+ }
392
+ ```
393
+
394
+ | Input | Type | Default |
395
+ |---|---|---|
396
+ | `data` | `any[]` | `[]` |
397
+ | `columns` | `Column[]` | `[]` |
398
+ | `rowsPerPage` | `number` | `10` |
399
+ | `rowsPerPageOptions` | `number[]` | `[10, 25, 50]` |
400
+ | `loading` | `boolean` | `false` |
401
+ | `showActionRow` | `boolean` | `true` |
402
+ | `headerActions` | `TableAction[]` | `[]` |
403
+ | `rowActions` | `RowAction[]` | `[]` |
404
+ | `hasShadow` | `boolean` | `true` |
405
+ | `defaultSortField` | `string` | `''` |
406
+ | `defaultSortOrder` | `number` | `1` |
407
+ | `showSearch` | `boolean` | `true` |
408
+ | `searchPlaceholder` | `string` | `''` |
409
+ | `emptyMessage` | `string` | `''` |
410
+ | `serverSide` | `boolean` | `false` |
411
+ | `totalRecords` | `number` | `0` |
412
+ | `filters` | `{ label: string; value: string }[]` | `[]` |
413
+ | `activeFilter` | `string` | `''` |
414
+ | `customTemplates` | `{ [key: string]: any }` | `{}` |
415
+
416
+ | Output | Type |
417
+ |---|---|
418
+ | `pageChange` | `PageChangeEvent` |
419
+ | `filterChange` | `FilterChangeEvent` |
420
+ | `searchChange` | `string` |
421
+ | `filterClick` | `string` |
422
+ | `refresh` | `void` |
423
+
424
+ ---
425
+
426
+ ### Modal (`shk-modal`)
427
+
428
+ Dynamic modal that loads components via `ModalService`. Supports forms, fullscreen expansion, and accept/cancel buttons.
429
+
430
+ ```html
431
+ <shk-modal />
432
+ ```
433
+
434
+ ```typescript
435
+ interface ModalConfig {
436
+ title: string;
437
+ component: Type<any>;
438
+ data?: Record<string, any>;
439
+ width?: string;
440
+ showButtons?: boolean;
441
+ showExpandButton?: boolean;
442
+ acceptLabel?: string;
443
+ cancelLabel?: string;
444
+ onClose?: () => void;
445
+ }
446
+ ```
447
+
448
+ The injected component **must** expose:
449
+ - `form?: FormGroup` (optional, for validation)
450
+ - `onSubmit()` → calls `submitSuccess` or `submitError`
451
+ - `submitSuccess?: EventEmitter<void>` (optional)
452
+ - `submitError?: EventEmitter<void>` (optional)
453
+ - `handleCancel?: () => void` (optional)
454
+
455
+ ```typescript
456
+ constructor(private modalSrv: ModalService) {}
457
+
458
+ openModal() {
459
+ this.modalSrv.open({
460
+ title: 'Edit user',
461
+ component: EditUserComponent,
462
+ data: { userId: 123 },
463
+ width: '600px',
464
+ showButtons: true,
465
+ showExpandButton: true,
466
+ });
467
+ }
468
+
469
+ closeModal() {
470
+ this.modalSrv.close();
471
+ }
472
+ ```
473
+
474
+ **Service methods:**
475
+
476
+ | Method | Description |
477
+ |---|---|
478
+ | `open(config: ModalConfig)` | Opens a new modal |
479
+ | `close()` | Closes the current modal |
480
+ | `accept()` | Closes the modal (alias for close) |
481
+ | `clear()` | Closes all modals |
482
+
483
+ ---
484
+
485
+ ### Notification (`shk-notification`)
486
+
487
+ Toast notification system with configurable positions and optional progress bar.
488
+
489
+ ```html
490
+ <shk-notification position="right-top" />
491
+ ```
492
+
493
+ ```typescript
494
+ interface Notification {
495
+ id: string;
496
+ message: string;
497
+ type: 'success' | 'error' | 'warning' | 'info';
498
+ progress?: number;
499
+ showProgress?: boolean;
500
+ }
501
+ ```
502
+
503
+ ```typescript
504
+ constructor(private notifSrv: NotificationService) {}
505
+
506
+ showNotif() {
507
+ const id = this.notifSrv.addNotification(
508
+ 'Operation successful',
509
+ 'success',
510
+ false, // showProgress
511
+ 3000 // duration (ms)
512
+ );
513
+ }
514
+
515
+ updateProgress(id: string, progress: number) {
516
+ this.notifSrv.updateProgress(id, progress);
517
+ }
518
+ ```
519
+
520
+ | Input | Type | Default |
521
+ |---|---|---|
522
+ | `position` | `'center-top' \| 'right-top' \| 'left-top'` | `'center-top'` |
523
+
524
+ **Service methods:**
525
+
526
+ | Method | Description |
527
+ |---|---|
528
+ | `addNotification(msg, type?, showProgress?, duration?)` | Adds a notification |
529
+ | `updateProgress(id, progress)` | Updates the progress bar |
530
+ | `removeNotification(notification)` | Removes a notification |
531
+ | `removeNotificationById(id)` | Removes by ID |
532
+ | `clearAll()` | Removes all notifications |
533
+
534
+ ---
535
+
536
+ ### ConfirmDialog (`shk-confirm-dialog`)
537
+
538
+ Programmatic confirmation dialog. Used via `ConfirmDialogService` which dynamically creates it.
539
+
540
+ ```html
541
+ <!-- No need to add it to the template -->
542
+ ```
543
+
544
+ ```typescript
545
+ interface ConfirmConfig {
546
+ title?: string;
547
+ message: string;
548
+ confirmLabel?: string;
549
+ cancelLabel?: string;
550
+ loadingText?: string;
551
+ type?: 'danger' | 'info' | 'warning';
552
+ showCancel?: boolean;
553
+ loading?: boolean;
554
+ }
555
+ ```
556
+
557
+ ```typescript
558
+ constructor(private confirmSrv: ConfirmDialogService) {}
559
+
560
+ async deleteItem() {
561
+ const confirmed = await this.confirmSrv.confirm({
562
+ title: 'Delete user',
563
+ message: 'Are you sure you want to delete this user?',
564
+ confirmLabel: 'Delete',
565
+ type: 'danger',
566
+ });
567
+
568
+ if (confirmed) {
569
+ // proceed with deletion
570
+ }
571
+ }
572
+ ```
573
+
574
+ | Input | Type | Default |
575
+ |---|---|---|
576
+ | `title` | `string` | `'Confirm action'` |
577
+ | `message` | `string` | `'Are you sure you want to proceed?'` |
578
+ | `confirmLabel` | `string` | `'Confirm'` |
579
+ | `cancelLabel` | `string` | `'Cancel'` |
580
+ | `loadingText` | `string` | `'Processing…'` |
581
+ | `type` | `'danger' \| 'info' \| 'warning'` | `'danger'` |
582
+ | `loading` | `boolean` | `false` |
583
+ | `showCancel` | `boolean` | `true` |
584
+
585
+ | Output | Type |
586
+ |---|---|
587
+ | `closed` | `void` |
588
+
589
+ ---
590
+
591
+ ### Chart (`shk-chart`)
592
+
593
+ Chart.js wrapper supporting the main chart types.
594
+
595
+ ```html
596
+ <shk-chart
597
+ [type]="'bar'"
598
+ [data]="chartData"
599
+ [options]="chartOptions"
600
+ />
601
+ ```
602
+
603
+ ```typescript
604
+ import { COLOR_PALETTE, readThemeColors } from 'shirkasoft-ui-components';
605
+
606
+ const colors = readThemeColors(); // current theme colors
607
+ ```
608
+
609
+ | Input | Type | Default |
610
+ |---|---|---|
611
+ | `type` | `'line' \| 'bar' \| 'pie' \| 'doughnut' \| 'polarArea'` | `'bar'` |
612
+ | `data` | `any` | `{ datasets: [] }` |
613
+ | `options` | `any` | `{}` |
614
+
615
+ **Exported utils:**
616
+
617
+ | Export | Description |
618
+ |---|---|
619
+ | `COLOR_PALETTE` | Predefined color array (bg + border) |
620
+ | `readThemeColors()` | Reads current theme colors (`--shk-surface-*`) |
621
+
622
+ ---
623
+
624
+ ### Tooltip (`shk-tooltip`)
625
+
626
+ Tooltip with two modes: `hover` (CSS positioning) and `fixed` (JS-calculated positioning).
627
+
628
+ ```html
629
+ <shk-tooltip [text]="'Additional info'" [position]="'bottom'" [mode]="'hover'">
630
+ <button>Hover me</button>
631
+ </shk-tooltip>
632
+ ```
633
+
634
+ | Input | Type | Default |
635
+ |---|---|---|
636
+ | `text` | `string` | `''` |
637
+ | `position` | `'top' \| 'bottom' \| 'right'` | `'bottom'` |
638
+ | `mode` | `'hover' \| 'fixed'` | `'hover'` |
639
+ | `offset` | `number` | `8` |
640
+
641
+ ---
642
+
643
+ ## Development
644
+
645
+ ```bash
646
+ # Build the library
647
+ pnpm build
648
+
649
+ # Serve the showcase (demo app)
650
+ pnpm serve
651
+
652
+ # Build the showcase
653
+ pnpm build:showcase
654
+
655
+ # Watch for library changes
656
+ pnpm watch
657
+ ```
658
+
659
+ ### Versioning
660
+
661
+ ```bash
662
+ pnpm version:patch # 1.0.22 → 1.0.23
663
+ pnpm version:minor # 1.0.22 → 1.1.0
664
+ pnpm version:major # 1.0.22 → 2.0.0
665
+ ```
666
+
667
+ ---
668
+
669
+ ## Publishing
670
+
671
+ CI/CD via GitHub Actions:
672
+ - Pushing a `v*` tag automatically publishes to npm.
673
+ - Pushing to `main`/`master` deploys the showcase to GitHub Pages.
674
+
675
+ Manual:
676
+
677
+ ```bash
678
+ bash scripts/publish-lib.sh
679
+ ```
680
+
681
+ ---
682
+
683
+ ## License
684
+
685
+ MIT © Shirkasoft