mgv-backoffice 1.36.0 → 1.36.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1794 -1719
- package/dist/components/BaseBadge.vue.d.ts +1 -11
- package/dist/components/BaseDropdown.vue.d.ts +1 -1
- package/dist/ui-lib.js +942 -925
- package/dist/ui-lib.umd.cjs +1 -1
- package/dist/utils/specForm.d.ts +1 -1
- package/package.json +54 -54
- package/src/components/BaseAlert.vue +104 -104
- package/src/components/BaseBadge.vue +12 -2
- package/src/components/BaseBarDistribution.test.ts +51 -51
- package/src/components/BaseBarDistribution.vue +73 -73
- package/src/components/BaseChipButton.vue +42 -42
- package/src/components/BaseCodeBlock.test.ts +70 -70
- package/src/components/BaseCodeBlock.vue +74 -74
- package/src/components/BaseCollapsibleSection.vue +113 -113
- package/src/components/BaseConfirmModal.vue +114 -114
- package/src/components/BaseCredentialsForm.test.ts +108 -108
- package/src/components/BaseCredentialsForm.vue +241 -241
- package/src/components/BaseDropdown.test.ts +87 -87
- package/src/components/BaseDropdown.vue +172 -172
- package/src/components/BaseEntityPickerModal.vue +330 -330
- package/src/components/BaseFileDropzone.vue +94 -94
- package/src/components/BaseFilterChip.test.ts +44 -44
- package/src/components/BaseFilterChip.vue +67 -67
- package/src/components/BaseModalShell.vue +209 -209
- package/src/components/BasePageHeader.vue +120 -120
- package/src/components/BasePillPickerModal.test.ts +95 -95
- package/src/components/BaseRemoveButton.vue +22 -22
- package/src/components/BaseSpecFields.test.ts +39 -0
- package/src/components/BaseSpecFields.vue +27 -3
- package/src/components/BaseStatBreakdown.test.ts +56 -56
- package/src/components/BaseStatBreakdown.vue +49 -49
- package/src/components/BaseStatusPill.vue +54 -54
- package/src/components/BaseTextInputModal.vue +140 -140
- package/src/components/BaseToolbarButton.test.ts +48 -48
- package/src/components/BaseToolbarButton.vue +94 -94
- package/src/composables/useFieldClasses.test.ts +55 -55
- package/src/composables/useFieldClasses.ts +56 -56
- package/src/composables/usePolling.test.ts +112 -112
- package/src/composables/usePolling.ts +102 -102
- package/src/composables/useQueryParamSync.test.ts +56 -56
- package/src/composables/useQueryParamSync.ts +42 -42
- package/src/composables/useThemeClasses.test.ts +34 -34
- package/src/composables/useThemeClasses.ts +137 -137
- package/src/index.ts +138 -138
- package/src/style.css +32 -32
- package/src/types/distribution.ts +7 -7
- package/src/types/pillPicker.ts +14 -14
- package/src/types/specField.ts +30 -30
- package/src/types/statBreakdown.ts +6 -6
- package/src/utils/format.test.ts +264 -264
- package/src/utils/format.ts +216 -216
- package/src/utils/httpColors.test.ts +100 -100
- package/src/utils/httpColors.ts +99 -99
- package/src/utils/kvRows.test.ts +47 -47
- package/src/utils/kvRows.ts +34 -34
- package/src/utils/specForm.test.ts +64 -53
- package/src/utils/specForm.ts +49 -48
- package/src/utils/validate.test.ts +82 -82
- package/src/utils/validate.ts +76 -76
package/README.md
CHANGED
|
@@ -1,1719 +1,1794 @@
|
|
|
1
|
-
# mgv-backoffice
|
|
2
|
-
|
|
3
|
-
Shared Vue 3 UI component library built with TypeScript and Tailwind CSS.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install mgv-backoffice
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
### Peer Dependencies
|
|
12
|
-
|
|
13
|
-
These must be installed in your project:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install vue@^3.5.0 vue-router@^5.0.0 @heroicons/vue@^2.0.0
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
> `vue-router` 5.x is required (peer range `^5.0.0`) — it's what the library is
|
|
20
|
-
> developed and tested against. Upgrade from Router 4 before installing this library.
|
|
21
|
-
|
|
22
|
-
### Import Styles
|
|
23
|
-
|
|
24
|
-
Include the library's stylesheet in your app entry point:
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
import 'mgv-backoffice/dist/style.css'
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Tailwind Safelist
|
|
31
|
-
|
|
32
|
-
If your project uses Tailwind, import the safelist so dynamic classes used by this library are generated correctly:
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
// In your Tailwind config
|
|
36
|
-
import safelist from 'mgv-backoffice/tailwind.safelist'
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Or include the pre-built CSS safelist:
|
|
40
|
-
|
|
41
|
-
```css
|
|
42
|
-
@import 'mgv-backoffice/tailwind.safelist.css';
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
> Maintainers: `tailwind.safelist.js` is the single source of truth.
|
|
46
|
-
> `tailwind.safelist.css` is generated from it via `npm run safelist`
|
|
47
|
-
> (runs automatically before `npm run build`) — don't edit it by hand.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## Components
|
|
52
|
-
|
|
53
|
-
### BaseAlert
|
|
54
|
-
|
|
55
|
-
Inline notice panel with color-coded variants (error / warning / success /
|
|
56
|
-
info), theme-aware via the shared `isDark` ref. The optional default slot
|
|
57
|
-
renders body content under the title, and `compact` gives a slim text-xs
|
|
58
|
-
variant for in-form warnings.
|
|
59
|
-
|
|
60
|
-
**Props:**
|
|
61
|
-
|
|
62
|
-
| Prop | Type | Default | Description |
|
|
63
|
-
| --------- | ----------- | ------------------ | ------------------------ |
|
|
64
|
-
| `title` | `String` | `''` | Bold headline (optional when the slot carries the message). |
|
|
65
|
-
| `color` | `AlertEnum` | `AlertEnum.ERROR` | Alert color variant. |
|
|
66
|
-
| `compact` | `Boolean` | `false` | Slim variant: text-xs, smaller icon/padding. |
|
|
67
|
-
|
|
68
|
-
**Slots:** default — body content rendered under the title.
|
|
69
|
-
|
|
70
|
-
**Example:**
|
|
71
|
-
|
|
72
|
-
```vue
|
|
73
|
-
<template>
|
|
74
|
-
<BaseAlert title="Operation successful" :color="AlertEnum.SUCCESS" />
|
|
75
|
-
<BaseAlert title="Proxying is active." :color="AlertEnum.WARNING">
|
|
76
|
-
<p class="mt-0.5 text-xs opacity-90">The canned response below is ignored.</p>
|
|
77
|
-
</BaseAlert>
|
|
78
|
-
<BaseAlert compact :color="AlertEnum.WARNING">
|
|
79
|
-
Chunked dribble is ignored while Fault Simulation is active.
|
|
80
|
-
</BaseAlert>
|
|
81
|
-
</template>
|
|
82
|
-
|
|
83
|
-
<script setup lang="ts">
|
|
84
|
-
import { BaseAlert, AlertEnum } from 'mgv-backoffice'
|
|
85
|
-
</script>
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
### BaseBadge
|
|
91
|
-
|
|
92
|
-
Colored status badge/pill.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
<BaseBadge :color="ColorsEnums.
|
|
108
|
-
</
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
<BaseButton
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
<
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
|
277
|
-
|
|
|
278
|
-
| `
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
|
379
|
-
|
|
|
380
|
-
| `
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
</
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
<
|
|
420
|
-
|
|
421
|
-
</
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
|
439
|
-
|
|
|
440
|
-
| `
|
|
441
|
-
| `
|
|
442
|
-
| `
|
|
443
|
-
| `
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
|
521
|
-
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
<
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
|
598
|
-
|
|
|
599
|
-
| `
|
|
600
|
-
| `
|
|
601
|
-
| `
|
|
602
|
-
| `
|
|
603
|
-
| `
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
```ts
|
|
636
|
-
import {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
|
697
|
-
|
|
|
698
|
-
| `
|
|
699
|
-
| `
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
sanitizeHtml(
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
|
759
|
-
|
|
|
760
|
-
| `
|
|
761
|
-
| `
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
|
828
|
-
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
|
850
|
-
|
|
|
851
|
-
| `
|
|
852
|
-
| `
|
|
853
|
-
| `
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
|
967
|
-
|
|
|
968
|
-
| `
|
|
969
|
-
| `loading`
|
|
970
|
-
| `
|
|
971
|
-
| `
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
```
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
**
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
`
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
`
|
|
1054
|
-
`
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
`
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
**
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
**
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
**
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
|
1317
|
-
|
|
1318
|
-
**
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
|
1341
|
-
|
|
|
1342
|
-
| `
|
|
1343
|
-
| `
|
|
1344
|
-
| `
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
###
|
|
1360
|
-
|
|
1361
|
-
Themed
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
**Props:**
|
|
1367
|
-
|
|
1368
|
-
| Prop
|
|
1369
|
-
|
|
|
1370
|
-
| `
|
|
1371
|
-
| `
|
|
1372
|
-
| `size`
|
|
1373
|
-
| `
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
**Props:**
|
|
1416
|
-
|
|
1417
|
-
| Prop
|
|
1418
|
-
|
|
|
1419
|
-
| `
|
|
1420
|
-
| `
|
|
1421
|
-
| `
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
**Props:**
|
|
1444
|
-
|
|
1445
|
-
| Prop | Type
|
|
1446
|
-
| ------------- |
|
|
1447
|
-
| `options` | `
|
|
1448
|
-
| `modelValue` | `String \| Number`
|
|
1449
|
-
| `
|
|
1450
|
-
| `
|
|
1451
|
-
| `
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
```vue
|
|
1508
|
-
<
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
|
1527
|
-
|
|
|
1528
|
-
| `
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
```
|
|
1541
|
-
|
|
1542
|
-
###
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
`
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
`
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
**
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
:
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
`
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1
|
+
# mgv-backoffice
|
|
2
|
+
|
|
3
|
+
Shared Vue 3 UI component library built with TypeScript and Tailwind CSS.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install mgv-backoffice
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Peer Dependencies
|
|
12
|
+
|
|
13
|
+
These must be installed in your project:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install vue@^3.5.0 vue-router@^5.0.0 @heroicons/vue@^2.0.0
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> `vue-router` 5.x is required (peer range `^5.0.0`) — it's what the library is
|
|
20
|
+
> developed and tested against. Upgrade from Router 4 before installing this library.
|
|
21
|
+
|
|
22
|
+
### Import Styles
|
|
23
|
+
|
|
24
|
+
Include the library's stylesheet in your app entry point:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import 'mgv-backoffice/dist/style.css'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Tailwind Safelist
|
|
31
|
+
|
|
32
|
+
If your project uses Tailwind, import the safelist so dynamic classes used by this library are generated correctly:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
// In your Tailwind config
|
|
36
|
+
import safelist from 'mgv-backoffice/tailwind.safelist'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or include the pre-built CSS safelist:
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
@import 'mgv-backoffice/tailwind.safelist.css';
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> Maintainers: `tailwind.safelist.js` is the single source of truth.
|
|
46
|
+
> `tailwind.safelist.css` is generated from it via `npm run safelist`
|
|
47
|
+
> (runs automatically before `npm run build`) — don't edit it by hand.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Components
|
|
52
|
+
|
|
53
|
+
### BaseAlert
|
|
54
|
+
|
|
55
|
+
Inline notice panel with color-coded variants (error / warning / success /
|
|
56
|
+
info), theme-aware via the shared `isDark` ref. The optional default slot
|
|
57
|
+
renders body content under the title, and `compact` gives a slim text-xs
|
|
58
|
+
variant for in-form warnings.
|
|
59
|
+
|
|
60
|
+
**Props:**
|
|
61
|
+
|
|
62
|
+
| Prop | Type | Default | Description |
|
|
63
|
+
| --------- | ----------- | ------------------ | ------------------------ |
|
|
64
|
+
| `title` | `String` | `''` | Bold headline (optional when the slot carries the message). |
|
|
65
|
+
| `color` | `AlertEnum` | `AlertEnum.ERROR` | Alert color variant. |
|
|
66
|
+
| `compact` | `Boolean` | `false` | Slim variant: text-xs, smaller icon/padding. |
|
|
67
|
+
|
|
68
|
+
**Slots:** default — body content rendered under the title.
|
|
69
|
+
|
|
70
|
+
**Example:**
|
|
71
|
+
|
|
72
|
+
```vue
|
|
73
|
+
<template>
|
|
74
|
+
<BaseAlert title="Operation successful" :color="AlertEnum.SUCCESS" />
|
|
75
|
+
<BaseAlert title="Proxying is active." :color="AlertEnum.WARNING">
|
|
76
|
+
<p class="mt-0.5 text-xs opacity-90">The canned response below is ignored.</p>
|
|
77
|
+
</BaseAlert>
|
|
78
|
+
<BaseAlert compact :color="AlertEnum.WARNING">
|
|
79
|
+
Chunked dribble is ignored while Fault Simulation is active.
|
|
80
|
+
</BaseAlert>
|
|
81
|
+
</template>
|
|
82
|
+
|
|
83
|
+
<script setup lang="ts">
|
|
84
|
+
import { BaseAlert, AlertEnum } from 'mgv-backoffice'
|
|
85
|
+
</script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### BaseBadge
|
|
91
|
+
|
|
92
|
+
Colored status badge/pill. Renders nothing when the default slot is empty;
|
|
93
|
+
an omitted or unrecognized `color` falls back to the red palette.
|
|
94
|
+
|
|
95
|
+
**Props:**
|
|
96
|
+
|
|
97
|
+
| Prop | Type | Default | Description |
|
|
98
|
+
| ------- | -------- | ------- | --------------------------------- |
|
|
99
|
+
| `color` | `String` | — | Color variant (use `ColorsEnums`) |
|
|
100
|
+
|
|
101
|
+
**Slots:** `default` — badge label content.
|
|
102
|
+
|
|
103
|
+
**Example:**
|
|
104
|
+
|
|
105
|
+
```vue
|
|
106
|
+
<template>
|
|
107
|
+
<BaseBadge :color="ColorsEnums.GREEN">Active</BaseBadge>
|
|
108
|
+
<BaseBadge :color="ColorsEnums.RED">Inactive</BaseBadge>
|
|
109
|
+
</template>
|
|
110
|
+
|
|
111
|
+
<script setup lang="ts">
|
|
112
|
+
import { BaseBadge, ColorsEnums } from 'mgv-backoffice'
|
|
113
|
+
</script>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### BaseBreadcrumb
|
|
119
|
+
|
|
120
|
+
Breadcrumb navigation. Provide items manually or pass a URL path for
|
|
121
|
+
auto-generation; with neither prop it auto-generates from
|
|
122
|
+
`window.location.pathname`. A "Home" crumb linking to `/` is always prepended.
|
|
123
|
+
|
|
124
|
+
**Props:**
|
|
125
|
+
|
|
126
|
+
| Prop | Type | Default | Description |
|
|
127
|
+
| ------- | --------------- | ----------- | ------------------------------------------ |
|
|
128
|
+
| `items` | `BreadCrumb[]` | `undefined` | Manual breadcrumb entries |
|
|
129
|
+
| `path` | `String` | `undefined` | URL path for auto-generated breadcrumbs |
|
|
130
|
+
|
|
131
|
+
**BreadCrumb type:**
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
interface BreadCrumb {
|
|
135
|
+
name: string
|
|
136
|
+
url: string
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Example:**
|
|
141
|
+
|
|
142
|
+
```vue
|
|
143
|
+
<template>
|
|
144
|
+
<!-- Manual -->
|
|
145
|
+
<BaseBreadcrumb :items="[
|
|
146
|
+
{ name: 'Home', url: '/' },
|
|
147
|
+
{ name: 'Users', url: '/users' },
|
|
148
|
+
{ name: 'Profile', url: '/users/1' }
|
|
149
|
+
]" />
|
|
150
|
+
|
|
151
|
+
<!-- Auto-generated from path -->
|
|
152
|
+
<BaseBreadcrumb path="/users/settings/profile" />
|
|
153
|
+
</template>
|
|
154
|
+
|
|
155
|
+
<script setup lang="ts">
|
|
156
|
+
import { BaseBreadcrumb } from 'mgv-backoffice'
|
|
157
|
+
import type { BreadCrumb } from 'mgv-backoffice'
|
|
158
|
+
</script>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### BaseButton
|
|
164
|
+
|
|
165
|
+
Button with color, size, loading state, and Vue Router integration.
|
|
166
|
+
|
|
167
|
+
**Props:**
|
|
168
|
+
|
|
169
|
+
| Prop | Type | Default | Description |
|
|
170
|
+
| ------------- | ----------------------------------- | --------------------- | ------------------------------------ |
|
|
171
|
+
| `description` | `String` | **required** | Button label text |
|
|
172
|
+
| `color` | `String` | `BaseButtonEnum.BLUE` | Color variant (`BLUE`/`WHITE`/`DARK`/`GREEN`/`EMERALD`/`RED`/`YELLOW`/`PURPLE`/`SKY`/`GRAY`/`AMBER`) |
|
|
173
|
+
| `outline` | `Boolean` | `false` | Outlined/secondary style — transparent fill, coloured text + border, tinted hover (theme-aware) |
|
|
174
|
+
| `ghost` | `Boolean` | `false` | Ghost/borderless style — no border or fill, coloured text + tinted hover (theme-aware). For compact toolbar/action buttons |
|
|
175
|
+
| `to` | `String` | — | Vue Router path (renders `<router-link>`) |
|
|
176
|
+
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | HTML button type |
|
|
177
|
+
| `iconLeft` | `Boolean` | `false` | Render the slot icon before the label |
|
|
178
|
+
| `isRounded` | `Boolean` | — | Fully rounded corners |
|
|
179
|
+
| `isDisable` | `Boolean` | — | Disabled state |
|
|
180
|
+
| `size` | `String` | — | Size variant (use `BaseButtonSizeEnum`) |
|
|
181
|
+
| `isLoading` | `Boolean` | — | Show loading spinner |
|
|
182
|
+
|
|
183
|
+
**Slots:** `default`
|
|
184
|
+
|
|
185
|
+
**Example:**
|
|
186
|
+
|
|
187
|
+
```vue
|
|
188
|
+
<template>
|
|
189
|
+
<BaseButton description="Submit" :color="BaseButtonEnum.GREEN" type="submit" />
|
|
190
|
+
<BaseButton description="Go to Users" :to="'/users'" />
|
|
191
|
+
<BaseButton description="Saving..." :isLoading="true" :isDisable="true" />
|
|
192
|
+
<BaseButton
|
|
193
|
+
description="Delete"
|
|
194
|
+
:color="BaseButtonEnum.RED"
|
|
195
|
+
:size="BaseButtonSizeEnum.SMALL"
|
|
196
|
+
/>
|
|
197
|
+
<!-- Outlined / secondary -->
|
|
198
|
+
<BaseButton description="Import" :color="BaseButtonEnum.EMERALD" outline iconLeft>
|
|
199
|
+
<ArrowUpTrayIcon class="w-4 h-4 mr-1.5" />
|
|
200
|
+
</BaseButton>
|
|
201
|
+
<!-- Ghost / borderless toolbar action -->
|
|
202
|
+
<BaseButton description="Logs" :color="BaseButtonEnum.SKY" ghost iconLeft :size="BaseButtonSizeEnum.SMALL">
|
|
203
|
+
<ClipboardDocumentListIcon class="w-4 h-4 mr-1.5" />
|
|
204
|
+
</BaseButton>
|
|
205
|
+
</template>
|
|
206
|
+
|
|
207
|
+
<script setup lang="ts">
|
|
208
|
+
import { BaseButton, BaseButtonEnum, BaseButtonSizeEnum } from 'mgv-backoffice'
|
|
209
|
+
</script>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### BaseLine
|
|
215
|
+
|
|
216
|
+
Horizontal divider with style variants.
|
|
217
|
+
|
|
218
|
+
**Props:**
|
|
219
|
+
|
|
220
|
+
| Prop | Type | Default | Description |
|
|
221
|
+
| ------ | -------- | --------------- | ----------------- |
|
|
222
|
+
| `mode` | `String` | `LineEnum.BASE` | Divider style |
|
|
223
|
+
|
|
224
|
+
**Example:**
|
|
225
|
+
|
|
226
|
+
```vue
|
|
227
|
+
<template>
|
|
228
|
+
<BaseLine />
|
|
229
|
+
<BaseLine :mode="LineEnum.SQUARE" />
|
|
230
|
+
</template>
|
|
231
|
+
|
|
232
|
+
<script setup lang="ts">
|
|
233
|
+
import { BaseLine, LineEnum } from 'mgv-backoffice'
|
|
234
|
+
</script>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
### BaseLogo
|
|
240
|
+
|
|
241
|
+
SVG brand logo component.
|
|
242
|
+
|
|
243
|
+
**Props:**
|
|
244
|
+
|
|
245
|
+
| Prop | Type | Default | Description |
|
|
246
|
+
| ------ | -------- | ---------------------- | ------------ |
|
|
247
|
+
| `size` | `String` | `BaseLogoEnum.MEDIUM` | Logo size |
|
|
248
|
+
|
|
249
|
+
**Example:**
|
|
250
|
+
|
|
251
|
+
```vue
|
|
252
|
+
<template>
|
|
253
|
+
<BaseLogo :size="BaseLogoEnum.LARGE" />
|
|
254
|
+
</template>
|
|
255
|
+
|
|
256
|
+
<script setup lang="ts">
|
|
257
|
+
import { BaseLogo, BaseLogoEnum } from 'mgv-backoffice'
|
|
258
|
+
</script>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### BaseModal
|
|
264
|
+
|
|
265
|
+
> ⚠️ **Deprecated.** Prefer [`BaseConfirmModal`](#baseconfirmmodal) for confirm/cancel
|
|
266
|
+
> flows or [`BaseModalShell`](#basemodalshell) for custom dialogs — they support dark
|
|
267
|
+
> mode, teleport to `<body>`, and slot-based composition. Kept for backward
|
|
268
|
+
> compatibility.
|
|
269
|
+
|
|
270
|
+
Confirmation dialog with support for delete and success modes. `DELETE` mode renders
|
|
271
|
+
the confirm/cancel pair; any other mode renders the title, optional `description`,
|
|
272
|
+
the default slot, and a single OK button that emits `closeModal`.
|
|
273
|
+
|
|
274
|
+
**Props:**
|
|
275
|
+
|
|
276
|
+
| Prop | Type | Default | Description |
|
|
277
|
+
| ------------- | -------- | ----------- |-------------------------------------|
|
|
278
|
+
| `title` | `String` | **required**| Modal heading |
|
|
279
|
+
| `description` | `String` | — | Body text |
|
|
280
|
+
| `to` | `String` | `"/"` | Unused — declared for backward compatibility only; confirm just emits `confirmModal`, no navigation happens |
|
|
281
|
+
| `mode` | `String` | `'SUCCESS'` | Modal variant (use `BaseModalEnum`) |
|
|
282
|
+
|
|
283
|
+
**Events:**
|
|
284
|
+
|
|
285
|
+
| Event | Description |
|
|
286
|
+
| -------------- | ------------------------------- |
|
|
287
|
+
| `closeModal` | Emitted when modal is dismissed (cancel/OK button, backdrop click, or Escape) |
|
|
288
|
+
| `confirmModal` | Emitted on confirm action |
|
|
289
|
+
|
|
290
|
+
**Example:**
|
|
291
|
+
|
|
292
|
+
```vue
|
|
293
|
+
<template>
|
|
294
|
+
<BaseModal
|
|
295
|
+
title="Delete this item?"
|
|
296
|
+
description="This action cannot be undone."
|
|
297
|
+
:mode="BaseModalEnum.DELETE"
|
|
298
|
+
@closeModal="showModal = false"
|
|
299
|
+
@confirmModal="handleDelete"
|
|
300
|
+
/>
|
|
301
|
+
</template>
|
|
302
|
+
|
|
303
|
+
<script setup lang="ts">
|
|
304
|
+
import { ref } from 'vue'
|
|
305
|
+
import { BaseModal, BaseModalEnum } from 'mgv-backoffice'
|
|
306
|
+
|
|
307
|
+
const showModal = ref(true)
|
|
308
|
+
const handleDelete = () => { /* ... */ }
|
|
309
|
+
</script>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### BaseRow
|
|
315
|
+
|
|
316
|
+
Card-like content container with border and shadow.
|
|
317
|
+
|
|
318
|
+
**Props:**
|
|
319
|
+
|
|
320
|
+
| Prop | Type | Default | Description |
|
|
321
|
+
| --------- | -------- | --------- | ---------------------- |
|
|
322
|
+
| `bgColor` | `String` | `"bg-white"` | Background color — a full Tailwind class (e.g. `"bg-slate-50"`), not a bare color name |
|
|
323
|
+
|
|
324
|
+
**Slots:** `default` — row content.
|
|
325
|
+
|
|
326
|
+
**Example:**
|
|
327
|
+
|
|
328
|
+
```vue
|
|
329
|
+
<template>
|
|
330
|
+
<BaseRow>
|
|
331
|
+
<p>Card content goes here</p>
|
|
332
|
+
</BaseRow>
|
|
333
|
+
</template>
|
|
334
|
+
|
|
335
|
+
<script setup lang="ts">
|
|
336
|
+
import { BaseRow } from 'mgv-backoffice'
|
|
337
|
+
</script>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
### BaseSpinner
|
|
343
|
+
|
|
344
|
+
Animated loading spinner — a neutral ring with a coloured leading arc.
|
|
345
|
+
|
|
346
|
+
**Props:**
|
|
347
|
+
|
|
348
|
+
| Prop | Type | Default | Description |
|
|
349
|
+
|---------|----------------------------------------------------------------------------|----------|-------------------------------------------------------------|
|
|
350
|
+
| `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'sm'` | Diameter + ring thickness — 16 / 24 / 32 / 48px. |
|
|
351
|
+
| `color` | `'blue' \| 'emerald' \| 'sky' \| 'indigo' \| 'teal' \| 'purple' \| 'red' \| 'amber'` | `'blue'` | Colour of the spinning arc. The track stays neutral gray. |
|
|
352
|
+
|
|
353
|
+
With no props it renders the original 16px blue spinner, so existing call sites are unaffected.
|
|
354
|
+
|
|
355
|
+
**Example:**
|
|
356
|
+
|
|
357
|
+
```vue
|
|
358
|
+
<template>
|
|
359
|
+
<!-- legacy default -->
|
|
360
|
+
<BaseSpinner />
|
|
361
|
+
<!-- larger, themed -->
|
|
362
|
+
<BaseSpinner size="lg" color="emerald" />
|
|
363
|
+
</template>
|
|
364
|
+
|
|
365
|
+
<script setup lang="ts">
|
|
366
|
+
import { BaseSpinner } from 'mgv-backoffice'
|
|
367
|
+
</script>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
### BaseToast
|
|
373
|
+
|
|
374
|
+
Toast notification with positioning and auto-dismiss.
|
|
375
|
+
|
|
376
|
+
**Props:**
|
|
377
|
+
|
|
378
|
+
| Prop | Type | Default | Description |
|
|
379
|
+
| -------------- | ---------------- | --------- | ------------------------------------------ |
|
|
380
|
+
| `mode` | `BaseToastEnum` | **required** | Toast variant (SUCCESS, WARNING, ERROR) |
|
|
381
|
+
| `description` | `String` | **required** | Message text |
|
|
382
|
+
| `hasCloseIcon` | `Boolean` | `true` | Show close button |
|
|
383
|
+
| `positioning` | `String` | `PositioningEnum.TOP_RIGHT` | Screen position (use `PositioningEnum`) |
|
|
384
|
+
| `duration` | `Number` | `5000` | Auto-dismiss delay in milliseconds |
|
|
385
|
+
|
|
386
|
+
**Example:**
|
|
387
|
+
|
|
388
|
+
```vue
|
|
389
|
+
<template>
|
|
390
|
+
<BaseToast
|
|
391
|
+
:mode="BaseToastEnum.SUCCESS"
|
|
392
|
+
description="Changes saved successfully!"
|
|
393
|
+
:positioning="PositioningEnum.TOP_RIGHT"
|
|
394
|
+
/>
|
|
395
|
+
</template>
|
|
396
|
+
|
|
397
|
+
<script setup lang="ts">
|
|
398
|
+
import { BaseToast, BaseToastEnum, PositioningEnum } from 'mgv-backoffice'
|
|
399
|
+
</script>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
### ColoredSquares
|
|
405
|
+
|
|
406
|
+
Colored square indicator with randomized pastel accent.
|
|
407
|
+
|
|
408
|
+
**Props:**
|
|
409
|
+
|
|
410
|
+
| Prop | Type | Default | Description |
|
|
411
|
+
| ------- | -------- | ------- | --------------------------------- |
|
|
412
|
+
| `color` | `String` | — | Color variant (use `ColorsEnums`) |
|
|
413
|
+
|
|
414
|
+
**Slots:** `default` — label content.
|
|
415
|
+
|
|
416
|
+
**Example:**
|
|
417
|
+
|
|
418
|
+
```vue
|
|
419
|
+
<template>
|
|
420
|
+
<ColoredSquares :color="ColorsEnums.BLUE">Category A</ColoredSquares>
|
|
421
|
+
</template>
|
|
422
|
+
|
|
423
|
+
<script setup lang="ts">
|
|
424
|
+
import { ColoredSquares, ColorsEnums } from 'mgv-backoffice'
|
|
425
|
+
</script>
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
### EarningsCard
|
|
431
|
+
|
|
432
|
+
Earnings summary card with formatted currency display. Supports a signed P&L
|
|
433
|
+
mode that renders a red loss theme (and a downward trend glyph) for negative
|
|
434
|
+
amounts.
|
|
435
|
+
|
|
436
|
+
**Props:**
|
|
437
|
+
|
|
438
|
+
| Prop | Type | Default | Description |
|
|
439
|
+
| ---------- | -------- |-------------------------|----------------------|
|
|
440
|
+
| `title` | `String` | `'TOTAL EARNINGS'` | Card heading |
|
|
441
|
+
| `amount` | `Number` | `0` | Monetary value (a stringified number is coerced) |
|
|
442
|
+
| `subtitle` | `String` | `'Lifetime commission'` | Subheading text |
|
|
443
|
+
| `badge` | `String` | `''` | Optional badge label |
|
|
444
|
+
| `currency` | `String` | `'$'` | Currency symbol |
|
|
445
|
+
| `decimals` | `Number` | `2` | Fraction digits shown for the amount |
|
|
446
|
+
| `accent` | `'orange' \| 'emerald' \| 'red'` | `'orange'` | Card theme. `emerald` tints it green; `red` is the loss theme. |
|
|
447
|
+
| `signed` | `Boolean` | `false` | Treat `amount` as a signed P&L figure: a negative value automatically switches to the `red` loss theme and flips the trend glyph to point **down**; a non-negative value keeps the chosen `accent` and the upward glyph. |
|
|
448
|
+
| `compact` | `Boolean` | `false` | Dense variant for dashboards that tile many cards on one row: tighter padding, smaller type, trend glyph shrunk into the top-right corner. Don't combine with `badge` — both occupy the top-right corner. |
|
|
449
|
+
|
|
450
|
+
**Example:**
|
|
451
|
+
|
|
452
|
+
```vue
|
|
453
|
+
<template>
|
|
454
|
+
<!-- Always-positive total: original behaviour. -->
|
|
455
|
+
<EarningsCard
|
|
456
|
+
title="Monthly Revenue"
|
|
457
|
+
:amount="12500"
|
|
458
|
+
subtitle="April 2026"
|
|
459
|
+
currency="€"
|
|
460
|
+
/>
|
|
461
|
+
|
|
462
|
+
<!-- Signed P&L: renders red + a down arrow when the amount is negative. -->
|
|
463
|
+
<EarningsCard
|
|
464
|
+
title="TOTAL P&L"
|
|
465
|
+
:amount="-128.4"
|
|
466
|
+
subtitle="Realised + unrealised"
|
|
467
|
+
accent="emerald"
|
|
468
|
+
signed
|
|
469
|
+
/>
|
|
470
|
+
</template>
|
|
471
|
+
|
|
472
|
+
<script setup lang="ts">
|
|
473
|
+
import { EarningsCard } from 'mgv-backoffice'
|
|
474
|
+
</script>
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
### EuroAmount
|
|
480
|
+
|
|
481
|
+
Formatted euro currency display with conditional color coding.
|
|
482
|
+
|
|
483
|
+
**Props:**
|
|
484
|
+
|
|
485
|
+
| Prop | Type | Default | Description |
|
|
486
|
+
| -------------- | --------- | ------- |-------------------------------------------------|
|
|
487
|
+
| `amount` | `Number` | — | Value to display |
|
|
488
|
+
| `beforeAmount` | `Number` | `null` | Previous value: red if `amount < beforeAmount`, green if `amount >= beforeAmount`. When omitted, a non-negative amount renders neutral (never green); a negative amount is always red. |
|
|
489
|
+
| `showCurrency` | `Boolean` | `true` | Show euro symbol |
|
|
490
|
+
|
|
491
|
+
**Example:**
|
|
492
|
+
|
|
493
|
+
```vue
|
|
494
|
+
<template>
|
|
495
|
+
<!-- Shows green (amount > beforeAmount) -->
|
|
496
|
+
<EuroAmount :amount="1500" :beforeAmount="1200" />
|
|
497
|
+
|
|
498
|
+
<!-- Shows red (negative) -->
|
|
499
|
+
<EuroAmount :amount="-300" />
|
|
500
|
+
|
|
501
|
+
<!-- Without currency symbol -->
|
|
502
|
+
<EuroAmount :amount="800" :showCurrency="false" />
|
|
503
|
+
</template>
|
|
504
|
+
|
|
505
|
+
<script setup lang="ts">
|
|
506
|
+
import { EuroAmount } from 'mgv-backoffice'
|
|
507
|
+
</script>
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
### Pagination
|
|
513
|
+
|
|
514
|
+
Page navigation with smart ellipsis for large page counts.
|
|
515
|
+
|
|
516
|
+
**Props:**
|
|
517
|
+
|
|
518
|
+
| Prop | Type | Default | Description |
|
|
519
|
+
| -------------- | -------- | ------- | ----------------------- |
|
|
520
|
+
| `totalItems` | `Number` | **required** | Total number of items |
|
|
521
|
+
| `itemsPerPage` | `Number` | `20` | Items shown per page |
|
|
522
|
+
|
|
523
|
+
**Events:**
|
|
524
|
+
|
|
525
|
+
| Event | Payload | Description |
|
|
526
|
+
| -------------- | -------- |----------------------------------|
|
|
527
|
+
| `page-changed` | `Number` | Emitted with the new page number |
|
|
528
|
+
|
|
529
|
+
**Example:**
|
|
530
|
+
|
|
531
|
+
```vue
|
|
532
|
+
<template>
|
|
533
|
+
<Pagination
|
|
534
|
+
:totalItems="200"
|
|
535
|
+
:itemsPerPage="10"
|
|
536
|
+
@page-changed="onPageChange"
|
|
537
|
+
/>
|
|
538
|
+
</template>
|
|
539
|
+
|
|
540
|
+
<script setup lang="ts">
|
|
541
|
+
import { Pagination } from 'mgv-backoffice'
|
|
542
|
+
|
|
543
|
+
const onPageChange = (page: number) => {
|
|
544
|
+
console.log('Page:', page)
|
|
545
|
+
}
|
|
546
|
+
</script>
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
### TrendArrow
|
|
552
|
+
|
|
553
|
+
Up/down trend indicator displayed as a colored badge.
|
|
554
|
+
|
|
555
|
+
**Props:**
|
|
556
|
+
|
|
557
|
+
| Prop | Type | Default | Description |
|
|
558
|
+
| -------- | -------- | ------- |------------------------------------------------------|
|
|
559
|
+
| `number` | `Number` | — | Positive = green arrow up, negative = red arrow down, zero = neutral gray dash |
|
|
560
|
+
| `icon` | `String` | — | Optional suffix appended after the number (e.g. `"%"`) |
|
|
561
|
+
|
|
562
|
+
**Example:**
|
|
563
|
+
|
|
564
|
+
```vue
|
|
565
|
+
<template>
|
|
566
|
+
<TrendArrow :number="12.5" /> <!-- Green up arrow -->
|
|
567
|
+
<TrendArrow :number="-3.2" /> <!-- Red down arrow -->
|
|
568
|
+
<TrendArrow :number="0" /> <!-- Neutral gray dash -->
|
|
569
|
+
</template>
|
|
570
|
+
|
|
571
|
+
<script setup lang="ts">
|
|
572
|
+
import { TrendArrow } from 'mgv-backoffice'
|
|
573
|
+
</script>
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Enums
|
|
579
|
+
|
|
580
|
+
All enums are importable directly from the package:
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
import {
|
|
584
|
+
AlertEnum,
|
|
585
|
+
BaseBadgeEnum,
|
|
586
|
+
BaseButtonEnum,
|
|
587
|
+
BaseButtonSizeEnum,
|
|
588
|
+
BaseLogoEnum,
|
|
589
|
+
BaseModalEnum,
|
|
590
|
+
BaseToastEnum,
|
|
591
|
+
ColorsEnums,
|
|
592
|
+
LineEnum,
|
|
593
|
+
PositioningEnum
|
|
594
|
+
} from 'mgv-backoffice'
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
| Enum | Values |
|
|
598
|
+
| -------------------- |---------------------------------------------------------------|
|
|
599
|
+
| `AlertEnum` | `WARNING`, `ERROR`, `SUCCESS`, `INFO` |
|
|
600
|
+
| `BaseBadgeEnum` | `WIN`, `LOSE` |
|
|
601
|
+
| `BaseButtonEnum` | `RED`, `BLUE`, `WHITE`, `DARK`, `GREEN`, `EMERALD`, `YELLOW`, `PURPLE`, `SKY`, `GRAY`, `AMBER` |
|
|
602
|
+
| `BaseButtonSizeEnum` | `EXTRA_SMALL`, `SMALL`, `BASE`, `LARGE`, `EXTRA_LARGE` |
|
|
603
|
+
| `BaseLogoEnum` | `SMALL`, `MEDIUM`, `LARGE` (`BaseLoginEnum` is a deprecated alias) |
|
|
604
|
+
| `BaseModalEnum` | `DELETE`, `SUCCESS` |
|
|
605
|
+
| `BaseToastEnum` | `SUCCESS`, `WARNING`, `ERROR` |
|
|
606
|
+
| `ColorsEnums` | `NONE`, `RED`, `YELLOW`, `BLACK`, `GRAY`, `GREEN`, `BLUE` |
|
|
607
|
+
| `LineEnum` | `BASE`, `BASE_SHORTER`, `SQUARE` |
|
|
608
|
+
| `PositioningEnum` | `TOP_LEFT`, `TOP_RIGHT`, `BOTTOM_LEFT`, `BOTTOM_RIGHT` |
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## Types
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
import type { BreadCrumb, DropdownOption, PnL, PnLInputs } from 'mgv-backoffice'
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
| Type | Shape |
|
|
619
|
+
| ---------------- | -------------------------------------- |
|
|
620
|
+
| `BreadCrumb` | `{ name: string; url: string }` |
|
|
621
|
+
| `DropdownOption` | `{ value: string \| number; label: string; title?: string; disabled?: boolean }` |
|
|
622
|
+
| `PnLInputs` | `{ buyPrice; lastPrice; filledQty }` (each `number \| string \| null \| undefined`) |
|
|
623
|
+
| `PnL` | `{ pnlUsd: number \| null; pnlPct: number \| null }` |
|
|
624
|
+
|
|
625
|
+
The other exported types — `NavItem`, `NavSection`, `EntityPickerItem`,
|
|
626
|
+
`LoginCredentials`, `NotificationItem`, `SegmentedOption`, `TableColumn`,
|
|
627
|
+
`SpecField`, `SpecFieldType`, `SpecFieldValue`, `StatBreakdownItem`,
|
|
628
|
+
`PillPickerItem`, `DistributionBar`, `CredentialsView`, `CredentialsUpdate` —
|
|
629
|
+
are documented in their component's section.
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
## Utilities
|
|
634
|
+
|
|
635
|
+
```ts
|
|
636
|
+
import { getBaseColor, getBaseColorOf } from 'mgv-backoffice'
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
| Function | Signature | Returns |
|
|
640
|
+
| ---------------- | ---------------------------------- | ----------------------------------- |
|
|
641
|
+
| `getBaseColor` | `(c: AlertEnum) => string` | Tailwind color name for alert type |
|
|
642
|
+
| `getBaseColorOf` | `(c: ColorsEnums) => string` | Tailwind color name for color enum |
|
|
643
|
+
|
|
644
|
+
### HTTP colours
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
import {
|
|
648
|
+
methodBadgeSolid,
|
|
649
|
+
methodBadgeBright,
|
|
650
|
+
methodBadgeTinted,
|
|
651
|
+
statusBadgeSolid,
|
|
652
|
+
statusBadgeTinted,
|
|
653
|
+
statusBadgeSoft,
|
|
654
|
+
} from 'mgv-backoffice'
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
Tailwind class helpers for HTTP method and status code badges. `Solid` variants
|
|
658
|
+
return saturated `bg-*-600` classes for use on neutral surfaces; `Bright` /
|
|
659
|
+
`Tinted` variants return softer combinations suitable for cards. `statusBadgeTinted`
|
|
660
|
+
takes `(status, isDark)` to adapt between themes.
|
|
661
|
+
|
|
662
|
+
`methodBadgeTinted(method, isDark)` gives each method its own hue on a soft
|
|
663
|
+
tinted surface (`bg-*-500/15` dark / `bg-*-100` light; GET blue, POST emerald,
|
|
664
|
+
PUT amber, DELETE red, PATCH purple, HEAD sky) — the card-chip palette used by
|
|
665
|
+
WireMate's mock/stub cards. `statusBadgeSoft(status, isDark)` is its status
|
|
666
|
+
companion keyed by status class (emerald 2xx / sky 3xx / amber 4xx / red 5xx).
|
|
667
|
+
|
|
668
|
+
### Key/value row validators
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
import { rowKeyMissing, rowValueMissing } from 'mgv-backoffice'
|
|
672
|
+
import type { KeyValueRowLike } from 'mgv-backoffice'
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
| Function | Signature | Returns |
|
|
676
|
+
| ----------------- | ---------------------------------------------------------------------- | ------- |
|
|
677
|
+
| `rowValueMissing` | `(row: { key?, value?, matcherType? }) => boolean` | `true` when the row has a key but no value. |
|
|
678
|
+
| `rowKeyMissing` | `(row: { key?, value?, matcherType? }) => boolean` | `true` when the row has a value but no key. |
|
|
679
|
+
|
|
680
|
+
Consistency checks for dynamic key/value grids (header lists, query params,
|
|
681
|
+
metadata rows). Rows with `matcherType: 'absent'` are exempt — an absent
|
|
682
|
+
matcher intentionally carries no value.
|
|
683
|
+
|
|
684
|
+
### Input validators
|
|
685
|
+
|
|
686
|
+
```ts
|
|
687
|
+
import { isValidAbsoluteUrl, isValidJson, isValidXml, isValidBase64 } from 'mgv-backoffice'
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
Pure, dependency-free form-input validators. The payload validators treat
|
|
691
|
+
empty/whitespace-only input as **valid** — required-ness is a separate rule
|
|
692
|
+
from well-formedness; `isValidAbsoluteUrl` validates a value that must exist,
|
|
693
|
+
so empty is invalid there.
|
|
694
|
+
|
|
695
|
+
| Function | Signature | Returns |
|
|
696
|
+
| -------------------- | ---------------------------- | ------- |
|
|
697
|
+
| `isValidAbsoluteUrl` | `(value: string) => boolean` | `true` for an absolute `http://` / `https://` URL (other schemes rejected). |
|
|
698
|
+
| `isValidJson` | `(str: string) => boolean` | `true` when empty or parseable as JSON. |
|
|
699
|
+
| `isValidXml` | `(str: string) => boolean` | `true` when empty or well-formed XML (DOMParser `<parsererror>` check; browser-only). |
|
|
700
|
+
| `isValidBase64` | `(str: string) => boolean` | `true` when empty or well-formed base64 (whitespace stripped, length/alphabet checked, then `atob` as the final authority). |
|
|
701
|
+
|
|
702
|
+
### HTML sanitizer
|
|
703
|
+
|
|
704
|
+
```ts
|
|
705
|
+
import { sanitizeHtml, isSafeHref } from 'mgv-backoffice'
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
| Function | Signature | Returns |
|
|
709
|
+
| -------------- | ----------------------------------------------- | ------- |
|
|
710
|
+
| `sanitizeHtml` | `(raw: string \| undefined \| null) => string` | Allow-list–sanitised HTML safe for `v-html`. |
|
|
711
|
+
| `isSafeHref` | `(value: string) => boolean` | `true` if the href uses a safe scheme (http/https/mailto/tel, root-relative, or anchor) or is empty/whitespace-only. |
|
|
712
|
+
|
|
713
|
+
Allow-list sanitizer for strings bound into `v-html`. Keeps a small set of
|
|
714
|
+
formatting tags (`a`, `b`/`strong`, `i`/`em`, `code`, `pre`, `p`, `ul`/`ol`/`li`,
|
|
715
|
+
`span`, `div`, `br`), strips all other elements (unwrapping to text, or dropping
|
|
716
|
+
content entirely for `script`/`style`/`iframe`/etc.), removes every attribute
|
|
717
|
+
except `href`/`title` on anchors, keeps only allow-listed hrefs (http/https/
|
|
718
|
+
mailto/tel, root-relative `/`, anchors `#`, or empty — every other scheme such
|
|
719
|
+
as `javascript:`, `data:`, `ftp:` is stripped), and hardens surviving links with
|
|
720
|
+
`rel="noopener noreferrer" target="_blank"`. Browser-only (uses `DOMParser`).
|
|
721
|
+
|
|
722
|
+
```ts
|
|
723
|
+
sanitizeHtml('<p>Hi<script>alert(1)<\/script></p>') // '<p>Hi</p>'
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Display formatters
|
|
727
|
+
|
|
728
|
+
```ts
|
|
729
|
+
import {
|
|
730
|
+
fmtNumber,
|
|
731
|
+
fmtDate,
|
|
732
|
+
fmtDateTime,
|
|
733
|
+
fmtDateShort,
|
|
734
|
+
fmtPrice,
|
|
735
|
+
fmtPct,
|
|
736
|
+
fmtUsd,
|
|
737
|
+
// …plus fmtDateTimeMs, fmtCalendarDate, fmtCalendarDateTime,
|
|
738
|
+
// fmtMsAsSeconds, fmtBytes, fmtDuration, formatJson, stringifyValue
|
|
739
|
+
} from 'mgv-backoffice'
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
Locale-aware, pure, dependency-free formatters for tables, logs and charts.
|
|
743
|
+
`fmtNumber`, `fmtDate`, `fmtCalendarDate`, `fmtCalendarDateTime` and
|
|
744
|
+
`fmtDuration` handle missing/non-finite input gracefully (rendering an
|
|
745
|
+
em-dash) so raw API values can be passed without pre-sanitising; the
|
|
746
|
+
epoch/numeric formatters (`fmtDateTime`, `fmtDateShort`, `fmtDateTimeMs`,
|
|
747
|
+
`fmtPrice`, `fmtPct`, `fmtUsd`) expect valid input and will render
|
|
748
|
+
`"Invalid Date"` / `"NaN"` otherwise.
|
|
749
|
+
|
|
750
|
+
| Function | Signature | Returns |
|
|
751
|
+
| -------------- | --------------------------------------------------------------- | ------- |
|
|
752
|
+
| `fmtNumber` | `(n: number \| string \| null \| undefined, digits = 4) => string` | Fixed-fraction number; em-dash for null/undefined/non-finite. Accepts numeric strings. |
|
|
753
|
+
| `fmtDate` | `(s: string \| number \| null \| undefined) => string` | Locale date-time from ISO string or epoch; em-dash on empty, raw value on parse failure. |
|
|
754
|
+
| `fmtDateTime` | `(ms: number) => string` | Compact `"Mon D, HH:MM"` label from epoch-millis (chart axes/tooltips). |
|
|
755
|
+
| `fmtDateTimeMs`| `(s: string \| number) => string` | Full 24-hour locale date-time WITH the millisecond fraction — for dense feeds where same-second rows must stay distinguishable. |
|
|
756
|
+
| `fmtDateShort` | `(ms: number) => string` | Short `"Mon D"` calendar label from epoch-millis. |
|
|
757
|
+
| `fmtCalendarDate` | `(s: string \| number \| null \| undefined) => string` | `"Mon D, YYYY"` en-US calendar label; em-dash on empty, raw value on parse failure. |
|
|
758
|
+
| `fmtCalendarDateTime` | `(s: string \| number \| null \| undefined) => string` | `"Mon D, YYYY, HH:MM"` en-US calendar label with time of day. |
|
|
759
|
+
| `fmtMsAsSeconds` | `(ms: number \| null \| undefined) => string` | `"= 1.50 s"` magnitude hint for millisecond inputs (3 decimals below 1 s); `''` for non-positive input. |
|
|
760
|
+
| `fmtBytes` | `(bytes: number \| null \| undefined) => string` | `"512 B"` / `"1.5 KB"` / `"2.0 MB"`; `''` for zero/falsy input. |
|
|
761
|
+
| `fmtDuration` | `(ms: number \| null \| undefined, maxUnits = 2) => string` | Compact day/hour/minute duration, e.g. `"3d 5h"` / `"5h 12m"` / `"12m"`. Zero-value leading units are dropped; `maxUnits` caps how many units render. Em-dash for null/undefined/non-finite. |
|
|
762
|
+
| `fmtPrice` | `(n: number) => string` | Price with precision that scales to magnitude (more decimals for sub-cent values). |
|
|
763
|
+
| `fmtPct` | `(n: number, digits = 2) => string` | Percentage with explicit sign, e.g. `"+2.50%"`. |
|
|
764
|
+
| `fmtUsd` | `(v: number) => string` | Signed USD amount with leading sign, e.g. `"+$5.00"`. |
|
|
765
|
+
| `formatJson` | `(content: string) => string` | Pretty-prints parseable JSON with 2-space indentation; returns anything else verbatim. |
|
|
766
|
+
| `stringifyValue` | `(value: unknown) => string` | Display string for an unknown value: strings pass through, null/undefined → `''`, everything else JSON-serialized (`String()` fallback). |
|
|
767
|
+
|
|
768
|
+
### Spec-form helpers
|
|
769
|
+
|
|
770
|
+
```ts
|
|
771
|
+
import { buildSpecParams, firstInvalidNumericSpec } from 'mgv-backoffice'
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
Value-map helpers for spec-driven forms (the state behind `BaseSpecFields`).
|
|
775
|
+
A spec whose `default` is `null` is treated as OPTIONAL — blank means "knob
|
|
776
|
+
disabled" and passes validation.
|
|
777
|
+
|
|
778
|
+
| Function | Signature | Returns |
|
|
779
|
+
| ------------------------ | --------- | ------- |
|
|
780
|
+
| `buildSpecParams` | `(specs: SpecField[] \| undefined, existing: Record<string, SpecFieldValue>) => Record<string, SpecFieldValue>` | Value map seeded from each spec's `default`, keeping non-null overlapping values the caller already has (an existing `null` is re-seeded from the spec's `default`). |
|
|
781
|
+
| `firstInvalidNumericSpec`| `(specs: SpecField[] \| undefined, params: Record<string, SpecFieldValue>) => string \| null` | Label of the first blank / NaN numeric field, or `null` when all numerics are valid. |
|
|
782
|
+
|
|
783
|
+
### Profit & loss
|
|
784
|
+
|
|
785
|
+
```ts
|
|
786
|
+
import { computePnL } from 'mgv-backoffice'
|
|
787
|
+
import type { PnL, PnLInputs } from 'mgv-backoffice'
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
| Function | Signature | Returns |
|
|
791
|
+
| ------------ | ------------------------------- | ------- |
|
|
792
|
+
| `computePnL` | `(row: PnLInputs) => PnL` | Unrealised mark-to-market PnL in absolute USD and percent. Returns `{ pnlUsd: null, pnlPct: null }` when any input is missing, non-finite, or `buyPrice <= 0` / `lastPrice <= 0`. |
|
|
793
|
+
|
|
794
|
+
```ts
|
|
795
|
+
interface PnLInputs {
|
|
796
|
+
buyPrice: number | string | null | undefined
|
|
797
|
+
lastPrice: number | string | null | undefined
|
|
798
|
+
filledQty: number | string | null | undefined
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
interface PnL {
|
|
802
|
+
pnlUsd: number | null
|
|
803
|
+
pnlPct: number | null
|
|
804
|
+
}
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
```ts
|
|
808
|
+
computePnL({ buyPrice: 100, lastPrice: 110, filledQty: 5 })
|
|
809
|
+
// { pnlUsd: 50, pnlPct: 10 }
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
## Layout & shells (Tier 2 — full backoffice chrome)
|
|
815
|
+
|
|
816
|
+
### BaseAppLayout
|
|
817
|
+
|
|
818
|
+
Root layout: dark/light page background, skip link, `<main>`-with-inert wrapper.
|
|
819
|
+
The `<main>` content offset tracks the sidebar width automatically —
|
|
820
|
+
`lg:ml-60` when expanded, `lg:ml-16` when collapsed (via `useSidebarCollapse()`) —
|
|
821
|
+
and adds `pt-14 lg:pt-0` for the mobile top bar while the sidebar is shown.
|
|
822
|
+
|
|
823
|
+
**Props:**
|
|
824
|
+
|
|
825
|
+
| Prop | Type | Default | Description |
|
|
826
|
+
| ---- | ---- | ------- | ----------- |
|
|
827
|
+
| `showSidebar` | `Boolean` | `true` | Render the `sidebar` slot. Set false for full-bleed pages. |
|
|
828
|
+
| `skipLinkLabel` | `String` | `'Skip to main content'` | Label for the accessibility skip link. |
|
|
829
|
+
|
|
830
|
+
**Slots:** `sidebar`, `default` (page content).
|
|
831
|
+
|
|
832
|
+
```vue
|
|
833
|
+
<BaseAppLayout :show-sidebar="route.name !== 'presentation'">
|
|
834
|
+
<template #sidebar><AppSidebar /></template>
|
|
835
|
+
<RouterView />
|
|
836
|
+
</BaseAppLayout>
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
### BaseSidebar
|
|
840
|
+
|
|
841
|
+
Responsive sidebar with desktop fixed-positioning and mobile off-canvas
|
|
842
|
+
behavior, focus management, optional theme toggle, a desktop collapse
|
|
843
|
+
toggle (icon-only rail), an optional notifications bell, and configurable
|
|
844
|
+
nav sections.
|
|
845
|
+
|
|
846
|
+
**Props:**
|
|
847
|
+
|
|
848
|
+
| Prop | Type | Default | Description |
|
|
849
|
+
| ---- | ---- | ------- | ----------- |
|
|
850
|
+
| `sections` | `NavSection[]` | **required** | Grouped nav items. |
|
|
851
|
+
| `homeRouteName` | `String` | `'home'` | Route name for the logo / "go home" click. |
|
|
852
|
+
| `appName` | `String` | `''` | Optional app name in the footer. |
|
|
853
|
+
| `version` | `String` | `''` | Optional version string in the footer. |
|
|
854
|
+
| `showThemeToggle` | `Boolean` | `true` | Toggle the dark/light switch in the footer. |
|
|
855
|
+
| `collapsible` | `Boolean` | `true` | Show the desktop collapse toggle that shrinks the sidebar to an icon-only rail. |
|
|
856
|
+
| `showNotifications` | `Boolean` | `false` | Show the notifications bell (with unread badge) that toggles `BaseNotificationPanel`. |
|
|
857
|
+
|
|
858
|
+
> **Collapse state** is shared via `useSidebarCollapse()` (and persisted to
|
|
859
|
+
> localStorage) so `BaseAppLayout` can shrink the content offset from
|
|
860
|
+
> `lg:ml-60` to `lg:ml-16` in step with the rail. Collapsing only affects
|
|
861
|
+
> desktop (`lg+`); on mobile the sidebar stays a full off-canvas panel.
|
|
862
|
+
|
|
863
|
+
> **Notifications:** set `:show-notifications="true"` to render the bell,
|
|
864
|
+
> then drop a [`BaseNotificationPanel`](#basenotificationpanel) in your app.
|
|
865
|
+
> Both share state through `useNotifications()`, so the unread badge and the
|
|
866
|
+
> panel stay in sync.
|
|
867
|
+
|
|
868
|
+
**Slots:**
|
|
869
|
+
|
|
870
|
+
| Slot | Slot props | Description |
|
|
871
|
+
| -------- | ---------- | ----------- |
|
|
872
|
+
| `logo` | `{ size }` | Brand logo. Receives a `size` hint (28px in mobile bar, 52px in expanded sidebar, 36px in the collapsed rail). |
|
|
873
|
+
| `status` | — | Footer status row (e.g. health indicator, sync state). |
|
|
874
|
+
| `footer` | — | Replaces the default `appName` + `v{version}` line. |
|
|
875
|
+
|
|
876
|
+
**Types:**
|
|
877
|
+
|
|
878
|
+
```ts
|
|
879
|
+
import type { NavItem, NavSection } from 'mgv-backoffice'
|
|
880
|
+
|
|
881
|
+
interface NavItem {
|
|
882
|
+
name: string // Vue Router route name
|
|
883
|
+
label: string // display text
|
|
884
|
+
icon: Component // typically a Heroicon
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
interface NavSection {
|
|
888
|
+
title: string
|
|
889
|
+
items: NavItem[]
|
|
890
|
+
}
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
```vue
|
|
894
|
+
<BaseSidebar :sections="navSections" home-route-name="projects" app-name="WireMate UI" :version="appVersion">
|
|
895
|
+
<template #logo="{ size }"><WireMateLogo :size="size" /></template>
|
|
896
|
+
<template #status>
|
|
897
|
+
<HealthIndicator />
|
|
898
|
+
</template>
|
|
899
|
+
</BaseSidebar>
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
---
|
|
903
|
+
|
|
904
|
+
### BaseNotificationPanel
|
|
905
|
+
|
|
906
|
+
Left-anchored notification drawer (teleported to `<body>`, slides in from
|
|
907
|
+
the left, backdrop + Escape to close). Open/close state and the list live
|
|
908
|
+
in `useNotifications()`, so the sidebar bell and the panel stay in sync.
|
|
909
|
+
|
|
910
|
+
Enable the bell on the sidebar with `:show-notifications="true"`, drop one
|
|
911
|
+
`<BaseNotificationPanel />` anywhere in your app, and feed it data via the
|
|
912
|
+
composable.
|
|
913
|
+
|
|
914
|
+
**Props:**
|
|
915
|
+
|
|
916
|
+
| Prop | Type | Default | Description |
|
|
917
|
+
| ----------------- | --------- | ------------------------------ | ----------- |
|
|
918
|
+
| `title` | `String` | `'Notifications'` | Panel heading. |
|
|
919
|
+
| `emptyText` | `String` | `'You have no notifications.'` | Shown when the list is empty. |
|
|
920
|
+
| `showMarkAllRead` | `Boolean` | `true` | Render the "Mark all as read" action when there are unread items. |
|
|
921
|
+
|
|
922
|
+
**Emits:** `select` (the clicked notification's `id`; the row is also marked read).
|
|
923
|
+
|
|
924
|
+
```vue
|
|
925
|
+
<script setup lang="ts">
|
|
926
|
+
import { BaseNotificationPanel, useNotifications } from 'mgv-backoffice'
|
|
927
|
+
const { setNotifications } = useNotifications()
|
|
928
|
+
setNotifications([
|
|
929
|
+
{ id: 1, title: 'New comment', message: 'Alice replied to your post', time: '2m ago', type: 'info' },
|
|
930
|
+
{ id: 2, title: 'Build passed', time: '1h ago', read: true, type: 'success' },
|
|
931
|
+
])
|
|
932
|
+
</script>
|
|
933
|
+
|
|
934
|
+
<template>
|
|
935
|
+
<BaseSidebar :sections="navSections" :show-notifications="true" />
|
|
936
|
+
<BaseNotificationPanel @select="(id) => goTo(id)" />
|
|
937
|
+
</template>
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
```ts
|
|
941
|
+
import type { NotificationItem } from 'mgv-backoffice'
|
|
942
|
+
|
|
943
|
+
interface NotificationItem {
|
|
944
|
+
id: string | number
|
|
945
|
+
title: string
|
|
946
|
+
message?: string
|
|
947
|
+
time?: string // pre-formatted by you
|
|
948
|
+
read?: boolean
|
|
949
|
+
type?: 'info' | 'success' | 'warning' | 'error' // status dot colour
|
|
950
|
+
}
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
---
|
|
954
|
+
|
|
955
|
+
## Authentication
|
|
956
|
+
|
|
957
|
+
### BaseGoogleSignInButton
|
|
958
|
+
|
|
959
|
+
Google-branded "Sign in with Google" button (official multi-colour "G",
|
|
960
|
+
dark-mode surface swap). Purely presentational — it runs no OAuth itself;
|
|
961
|
+
listen on `click` and start your own Google Identity / Firebase / backend
|
|
962
|
+
flow there.
|
|
963
|
+
|
|
964
|
+
**Props:**
|
|
965
|
+
|
|
966
|
+
| Prop | Type | Default | Description |
|
|
967
|
+
| ---------- | --------- | -------------------------- | ----------- |
|
|
968
|
+
| `label` | `String` | `'Sign in with Google'` | Button text. |
|
|
969
|
+
| `loading` | `Boolean` | `false` | Disables and shows a spinner. |
|
|
970
|
+
| `disabled` | `Boolean` | `false` | Disables without the spinner. |
|
|
971
|
+
| `block` | `Boolean` | `true` | Full-width layout. |
|
|
972
|
+
|
|
973
|
+
**Emits:** `click` (only when not disabled/loading).
|
|
974
|
+
|
|
975
|
+
### BaseLoginForm
|
|
976
|
+
|
|
977
|
+
Presentational sign-in card: email + password (with show/hide), an optional
|
|
978
|
+
"Remember me" checkbox, an error banner, the Google button + "or" divider,
|
|
979
|
+
and `logo` / `forgot` / `footer` slots. Owns its input state and emits
|
|
980
|
+
`submit` / `google-sign-in`; the app handles the actual request and feeds
|
|
981
|
+
back `loading` / `error`.
|
|
982
|
+
|
|
983
|
+
**Props:**
|
|
984
|
+
|
|
985
|
+
| Prop | Type | Default | Description |
|
|
986
|
+
| --------------- | --------- | ----------- | ----------- |
|
|
987
|
+
| `title` | `String` | `'Sign in'` | Card heading. |
|
|
988
|
+
| `subtitle` | `String` | `''` | Muted line under the heading. |
|
|
989
|
+
| `submitLabel` | `String` | `'Sign in'` | Submit button text. |
|
|
990
|
+
| `loading` | `Boolean` | `false` | Disables the form, spinner on submit. |
|
|
991
|
+
| `googleLoading` | `Boolean` | `false` | Disables the form, spinner on the Google button. |
|
|
992
|
+
| `error` | `String` | `''` | Error banner above the form. |
|
|
993
|
+
| `showGoogle` | `Boolean` | `true` | Render the Google button + divider. |
|
|
994
|
+
| `showRemember` | `Boolean` | `false` | Render the "Remember me" checkbox. |
|
|
995
|
+
|
|
996
|
+
**Emits:** `submit` (`LoginCredentials`), `google-sign-in`.
|
|
997
|
+
|
|
998
|
+
**Slots:** `logo`, `forgot` (next to the password label), `footer`.
|
|
999
|
+
|
|
1000
|
+
```vue
|
|
1001
|
+
<script setup lang="ts">
|
|
1002
|
+
import { BaseLoginForm } from 'mgv-backoffice'
|
|
1003
|
+
import type { LoginCredentials } from 'mgv-backoffice'
|
|
1004
|
+
|
|
1005
|
+
async function onSubmit(creds: LoginCredentials) { /* call your API */ }
|
|
1006
|
+
function onGoogle() { /* start Google OAuth */ }
|
|
1007
|
+
</script>
|
|
1008
|
+
|
|
1009
|
+
<template>
|
|
1010
|
+
<BaseLoginForm
|
|
1011
|
+
subtitle="Welcome back"
|
|
1012
|
+
:show-remember="true"
|
|
1013
|
+
@submit="onSubmit"
|
|
1014
|
+
@google-sign-in="onGoogle"
|
|
1015
|
+
>
|
|
1016
|
+
<template #logo><MyLogo /></template>
|
|
1017
|
+
<template #forgot><a href="/forgot" class="text-sm text-emerald-600">Forgot?</a></template>
|
|
1018
|
+
<template #footer>No account? <a href="/signup" class="text-emerald-600">Sign up</a></template>
|
|
1019
|
+
</BaseLoginForm>
|
|
1020
|
+
</template>
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
---
|
|
1024
|
+
|
|
1025
|
+
## Modals & sections
|
|
1026
|
+
|
|
1027
|
+
### BaseModalShell
|
|
1028
|
+
|
|
1029
|
+
Shared modal chrome — `Teleport` to body, backdrop, themed card, escape key,
|
|
1030
|
+
aria-modal. Compose this rather than building modals from scratch.
|
|
1031
|
+
|
|
1032
|
+
**Props:**
|
|
1033
|
+
|
|
1034
|
+
| Prop | Type | Default | Description |
|
|
1035
|
+
| --------------- | --------- | ----------- | ----------- |
|
|
1036
|
+
| `title` | `String` | **required** | Modal heading. |
|
|
1037
|
+
| `icon` | `Component` | **required** | Heroicon rendered in a tinted circular chip left of the title (the `icon` slot can override the whole chip). |
|
|
1038
|
+
| `iconBgClass` | `String` | `''` | Background classes of the icon chip; empty falls back to the emerald tint (dark-mode aware). |
|
|
1039
|
+
| `iconClass` | `String` | `'text-emerald-600'` | Classes applied to the icon itself. |
|
|
1040
|
+
| `maxWidthClass` | `String` | `'max-w-md'` | Tailwind max-w utility for the card. |
|
|
1041
|
+
| `manualClose` | `Boolean` | `false` | If true, backdrop click and Escape do NOT auto-emit `cancel`. |
|
|
1042
|
+
| `scrollable` | `Boolean` | `false` | Switch to the large-content layout: a flex column capped at `90vh` with a fixed header/footer and a scrolling body. |
|
|
1043
|
+
| `subtitle` | `String` | `''` | Muted line under the title (scrollable layout only). |
|
|
1044
|
+
|
|
1045
|
+
**Slots:** `icon`, `default`, `footer`, and (scrollable layout) `header-actions` — content on the right of the header, e.g. a close button.
|
|
1046
|
+
**Events:** `cancel`, `backdrop`.
|
|
1047
|
+
|
|
1048
|
+
### BaseConfirmModal
|
|
1049
|
+
|
|
1050
|
+
Confirmation dialog built on `BaseModalShell`. Variant chooses red (danger) or
|
|
1051
|
+
amber (warning) styling.
|
|
1052
|
+
|
|
1053
|
+
**Props:** `title`, `message`, `confirmText`, `cancelText`, `submittingText`,
|
|
1054
|
+
`variant: 'danger' | 'warning'`, `submitting`. While `submitting` is true,
|
|
1055
|
+
backdrop clicks and Escape stop dismissing the dialog.
|
|
1056
|
+
|
|
1057
|
+
**Slots:** `message` — rich markup replacing the plain `message` string;
|
|
1058
|
+
`default` — extra content below the message (warning banner, opt-in checkbox).
|
|
1059
|
+
**Events:** `confirm`, `cancel`.
|
|
1060
|
+
|
|
1061
|
+
### BaseTextInputModal
|
|
1062
|
+
|
|
1063
|
+
"Ask the user for a single string and confirm" dialog. Preserves typed input
|
|
1064
|
+
on stray backdrop clicks; Escape always cancels.
|
|
1065
|
+
|
|
1066
|
+
**Props:** `title`, `message`, `initialValue`, `placeholder`, `inputLabel`,
|
|
1067
|
+
`confirmText`, `cancelText`, `submittingText`, `submitting`.
|
|
1068
|
+
|
|
1069
|
+
**Slots:** `icon` — override the default emerald document icon.
|
|
1070
|
+
**Events:** `confirm(value: string)`, `cancel`.
|
|
1071
|
+
|
|
1072
|
+
### BaseEntityPickerModal
|
|
1073
|
+
|
|
1074
|
+
Searchable "pick one from a list" dialog. Pass `items` directly or an async
|
|
1075
|
+
`loader` that runs on mount.
|
|
1076
|
+
|
|
1077
|
+
**Props:** `title`, `message?`, `items?: EntityPickerItem[]`,
|
|
1078
|
+
`loader?: () => Promise<EntityPickerItem[]>`, `excludeId?`,
|
|
1079
|
+
`variant: 'emerald' | 'purple' | 'blue' | 'red' | 'amber'`,
|
|
1080
|
+
`searchPlaceholder`, `emptyMessage`, `noMatchMessage`, `confirmText`,
|
|
1081
|
+
`cancelText`, `submittingText`, `submitting`.
|
|
1082
|
+
|
|
1083
|
+
**Slots:** `icon` — override the default icon chip.
|
|
1084
|
+
**Events:** `confirm(itemId: string)`, `cancel`.
|
|
1085
|
+
|
|
1086
|
+
```ts
|
|
1087
|
+
interface EntityPickerItem { id: string; label: string }
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
### BaseCollapsibleSection
|
|
1091
|
+
|
|
1092
|
+
Section wrapper with a clickable header, optional badge, and a `default` slot
|
|
1093
|
+
for the body. Parent owns the `collapsed` state.
|
|
1094
|
+
|
|
1095
|
+
**Props:** `title`, `collapsed`, `badge?`, `bodyClass?`.
|
|
1096
|
+
**Events:** `toggle`.
|
|
1097
|
+
|
|
1098
|
+
### BaseNotFoundPage
|
|
1099
|
+
|
|
1100
|
+
Drop-in 404 view.
|
|
1101
|
+
|
|
1102
|
+
**Props:** `code` (`'404'`), `message` (`'Page not found'`),
|
|
1103
|
+
`homeRouteName` (`'home'`), `homeLabel` (`'Go home'`).
|
|
1104
|
+
|
|
1105
|
+
### BasePageHeader
|
|
1106
|
+
|
|
1107
|
+
Page-level header: icon badge + title/subtitle on the left, action
|
|
1108
|
+
buttons on the right. Gives top-level views a consistent header shape
|
|
1109
|
+
and width. The icon badge only renders when the `icon` slot is filled,
|
|
1110
|
+
so icon-less apps get a plain title/subtitle header.
|
|
1111
|
+
|
|
1112
|
+
**Props:**
|
|
1113
|
+
|
|
1114
|
+
| Prop | Type | Default | Description |
|
|
1115
|
+
| --------------- | -------- | ------------- | ----------- |
|
|
1116
|
+
| `title` | `String` | **required** | H1 text. |
|
|
1117
|
+
| `subtitle` | `String` | `''` | Muted line below the title. |
|
|
1118
|
+
| `iconColor` | `String` | `'emerald'` | Badge + icon colour: `'emerald' \| 'sky' \| 'red' \| 'amber'`. |
|
|
1119
|
+
| `maxWidthClass` | `String` | `'max-w-4xl'` | Tailwind max-w utility constraining header width. Pass `''` to skip the width wrapper entirely — the header then spans its container. |
|
|
1120
|
+
| `align` | `String` | `'center'` | Vertical alignment of the title block vs the actions: `'center'` or `'end'` (actions sit on the title baseline). |
|
|
1121
|
+
| `marginClass` | `String` | `'mb-8'` | Space under the header. Pass `''` when the parent manages vertical rhythm (`space-y-*`). |
|
|
1122
|
+
|
|
1123
|
+
**Slots:**
|
|
1124
|
+
|
|
1125
|
+
| Slot | Slot props | Description |
|
|
1126
|
+
| ---------- | ----------------- | ----------- |
|
|
1127
|
+
| `icon` | `{ iconClass }` | Page Heroicon. Bind `:class="iconClass"` for the theme-aware colour. Badge square renders only when this slot is filled. |
|
|
1128
|
+
| `subtitle` | — | Rich subtitle content (links, `<strong>`, interpolation); overrides the `subtitle` prop. |
|
|
1129
|
+
| `actions` | — | Buttons rendered on the right (refresh, destructive, etc.). |
|
|
1130
|
+
|
|
1131
|
+
```vue
|
|
1132
|
+
<template>
|
|
1133
|
+
<BasePageHeader title="Request Journal" subtitle="Recent matched requests" icon-color="sky">
|
|
1134
|
+
<template #icon="{ iconClass }">
|
|
1135
|
+
<DocumentTextIcon class="w-5 h-5" :class="iconClass" />
|
|
1136
|
+
</template>
|
|
1137
|
+
<template #actions>
|
|
1138
|
+
<BaseButton description="Refresh" @click="reload" />
|
|
1139
|
+
</template>
|
|
1140
|
+
</BasePageHeader>
|
|
1141
|
+
</template>
|
|
1142
|
+
|
|
1143
|
+
<script setup lang="ts">
|
|
1144
|
+
import { BasePageHeader, BaseButton } from 'mgv-backoffice'
|
|
1145
|
+
import { DocumentTextIcon } from '@heroicons/vue/24/outline'
|
|
1146
|
+
</script>
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
---
|
|
1150
|
+
|
|
1151
|
+
### BaseToolbarButton
|
|
1152
|
+
|
|
1153
|
+
Bordered toolbar button — the "Refresh / Delete All" row that sits under
|
|
1154
|
+
a page header. Optional leading icon (via slot) plus a label.
|
|
1155
|
+
|
|
1156
|
+
**Props:**
|
|
1157
|
+
|
|
1158
|
+
| Prop | Type | Default | Description |
|
|
1159
|
+
| ---------- | --------- | ----------- | ----------- |
|
|
1160
|
+
| `label` | `String` | `''` | Button text. Omit for an icon-only button. |
|
|
1161
|
+
| `variant` | `String` | `'neutral'` | `'neutral'` (grey), `'danger'` (solid red) or `'ghost'` (slate h-9 outline — toolbar/modal-footer buttons). |
|
|
1162
|
+
| `disabled` | `Boolean` | `false` | Greys out and blocks the click. |
|
|
1163
|
+
| `title` | `String` | `undefined` | Native tooltip / a11y text. |
|
|
1164
|
+
| `type` | `String` | `'button'` | Native button type. |
|
|
1165
|
+
|
|
1166
|
+
**Slots:**
|
|
1167
|
+
|
|
1168
|
+
| Slot | Slot props | Description |
|
|
1169
|
+
| ------ | --------------- | ----------- |
|
|
1170
|
+
| `icon` | `{ iconClass }` | Leading Heroicon. Bind `:class="iconClass"` (`w-4 h-4`); add state classes as needed. |
|
|
1171
|
+
|
|
1172
|
+
**Emits:** `click` (native `MouseEvent`).
|
|
1173
|
+
|
|
1174
|
+
```vue
|
|
1175
|
+
<template>
|
|
1176
|
+
<BaseToolbarButton label="Refresh" :disabled="isLoading" title="Refresh" @click="reload">
|
|
1177
|
+
<template #icon="{ iconClass }">
|
|
1178
|
+
<ArrowPathIcon :class="[iconClass, { 'animate-spin': isLoading }]" />
|
|
1179
|
+
</template>
|
|
1180
|
+
</BaseToolbarButton>
|
|
1181
|
+
<BaseToolbarButton label="Delete All" variant="danger" @click="deleteAll">
|
|
1182
|
+
<template #icon="{ iconClass }">
|
|
1183
|
+
<TrashIcon :class="iconClass" />
|
|
1184
|
+
</template>
|
|
1185
|
+
</BaseToolbarButton>
|
|
1186
|
+
</template>
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
---
|
|
1190
|
+
|
|
1191
|
+
### BaseActionButton
|
|
1192
|
+
|
|
1193
|
+
Compact ghost action button — the colour-coded "Edit / Logs / Stub /
|
|
1194
|
+
Delete" actions on a card footer or action row. No border/fill at rest;
|
|
1195
|
+
a tinted hover background keyed to the semantic colour.
|
|
1196
|
+
|
|
1197
|
+
**Props:**
|
|
1198
|
+
|
|
1199
|
+
| Prop | Type | Default | Description |
|
|
1200
|
+
| ----------- | --------- | ----------- | ----------- |
|
|
1201
|
+
| `label` | `String` | `''` | Button text. Omit for an icon-only button. |
|
|
1202
|
+
| `color` | `String` | `'emerald'` | `'emerald' \| 'sky' \| 'indigo' \| 'teal' \| 'purple' \| 'red' \| 'amber' \| 'amberStrong'`. |
|
|
1203
|
+
| `disabled` | `Boolean` | `false` | Dims via opacity and suppresses the hover tint. |
|
|
1204
|
+
| `fullWidth` | `Boolean` | `false` | Stretch to fill its flex row (`flex-1`). |
|
|
1205
|
+
| `title` | `String` | `undefined` | Native tooltip. |
|
|
1206
|
+
| `ariaLabel` | `String` | `undefined` | Accessible label. |
|
|
1207
|
+
| `type` | `String` | `'button'` | Native button type. |
|
|
1208
|
+
|
|
1209
|
+
**Slots:**
|
|
1210
|
+
|
|
1211
|
+
| Slot | Slot props | Description |
|
|
1212
|
+
| ------ | --------------- | ----------- |
|
|
1213
|
+
| `icon` | `{ iconClass }` | Leading Heroicon. Bind `:class="iconClass"` (`w-4 h-4`). |
|
|
1214
|
+
|
|
1215
|
+
**Emits:** `click` (native `MouseEvent`).
|
|
1216
|
+
|
|
1217
|
+
```vue
|
|
1218
|
+
<template>
|
|
1219
|
+
<BaseActionButton label="Edit" color="emerald" full-width title="Edit this mock" @click="edit">
|
|
1220
|
+
<template #icon="{ iconClass }">
|
|
1221
|
+
<PencilSquareIcon :class="iconClass" />
|
|
1222
|
+
</template>
|
|
1223
|
+
</BaseActionButton>
|
|
1224
|
+
<BaseActionButton label="Delete" color="red" @click="remove">
|
|
1225
|
+
<template #icon="{ iconClass }">
|
|
1226
|
+
<TrashIcon :class="iconClass" />
|
|
1227
|
+
</template>
|
|
1228
|
+
</BaseActionButton>
|
|
1229
|
+
</template>
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
---
|
|
1233
|
+
|
|
1234
|
+
### BaseCopyButton
|
|
1235
|
+
|
|
1236
|
+
Copy-to-clipboard icon button with transient "copied" feedback — clicks
|
|
1237
|
+
write `text` to the clipboard, swap the clipboard icon for a checkmark
|
|
1238
|
+
for `resetMs`, then revert. Uses the async Clipboard API with a
|
|
1239
|
+
hidden-textarea `execCommand` fallback for insecure origins. Emits
|
|
1240
|
+
`copied` / `error` so the parent can fire its own toast.
|
|
1241
|
+
|
|
1242
|
+
**Props:**
|
|
1243
|
+
|
|
1244
|
+
| Prop | Type | Default | Description |
|
|
1245
|
+
| ----------- | --------- | --------- | ----------- |
|
|
1246
|
+
| `text` | `String` | **required** | Value written to the clipboard. |
|
|
1247
|
+
| `label` | `String` | `''` | Used in the tooltip / aria-label (`Copy {label}`). |
|
|
1248
|
+
| `variant` | `String` | `'ghost'` | `'ghost'` (borderless `p-1` icon) or `'bordered'` (`w-9 h-9` boxed, turns emerald while copied). |
|
|
1249
|
+
| `resetMs` | `Number` | `1500` | How long the checkmark stays before reverting. |
|
|
1250
|
+
| `iconClass` | `String` | `'w-4 h-4'` | Icon size class. |
|
|
1251
|
+
|
|
1252
|
+
**Emits:** `copied`, `error(err)`.
|
|
1253
|
+
|
|
1254
|
+
```vue
|
|
1255
|
+
<template>
|
|
1256
|
+
<!-- Inline ID copy, parent fires the toast -->
|
|
1257
|
+
<BaseCopyButton
|
|
1258
|
+
:text="stub.id"
|
|
1259
|
+
label="Stub ID"
|
|
1260
|
+
@copied="showToastMessage('Stub ID copied to clipboard', BaseToastEnum.SUCCESS)"
|
|
1261
|
+
@error="showToastMessage('Failed to copy stub ID', BaseToastEnum.ERROR)"
|
|
1262
|
+
/>
|
|
1263
|
+
<!-- Boxed copy next to a read-only input -->
|
|
1264
|
+
<BaseCopyButton :text="mock.id" label="Mock ID" variant="bordered" :reset-ms="2000" />
|
|
1265
|
+
</template>
|
|
1266
|
+
|
|
1267
|
+
<script setup lang="ts">
|
|
1268
|
+
import { BaseCopyButton, BaseToastEnum } from 'mgv-backoffice'
|
|
1269
|
+
</script>
|
|
1270
|
+
```
|
|
1271
|
+
|
|
1272
|
+
---
|
|
1273
|
+
|
|
1274
|
+
### BaseChipButton
|
|
1275
|
+
|
|
1276
|
+
Small tinted emerald "chip" action button — the compact "+ Add" pill used
|
|
1277
|
+
above repeatable form rows. Label comes from the default slot.
|
|
1278
|
+
|
|
1279
|
+
**Props:**
|
|
1280
|
+
|
|
1281
|
+
| Prop | Type | Default | Description |
|
|
1282
|
+
| ---------- | --------- | ------- | ----------- |
|
|
1283
|
+
| `size` | `String` | `'sm'` | `'sm'` = `px-2.5 py-1`; `'xs'` = `px-2 py-0.5` for tight corners. |
|
|
1284
|
+
| `disabled` | `Boolean` | `false` | Dims the chip and blocks clicks. |
|
|
1285
|
+
|
|
1286
|
+
**Emits:** `click`.
|
|
1287
|
+
|
|
1288
|
+
```vue
|
|
1289
|
+
<BaseChipButton @click="addRow(rows)">+ Add</BaseChipButton>
|
|
1290
|
+
<BaseChipButton size="xs" @click="addNamespace">+ Add</BaseChipButton>
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
---
|
|
1294
|
+
|
|
1295
|
+
### BaseRemoveButton
|
|
1296
|
+
|
|
1297
|
+
The red "×" remove-row affordance used beside repeatable form rows. Name it
|
|
1298
|
+
for screen readers via `aria-label`; `title`, `disabled` and extra classes
|
|
1299
|
+
(`pt-1`, `self-start`, …) fall through as attrs.
|
|
1300
|
+
|
|
1301
|
+
**Emits:** `click`.
|
|
1302
|
+
|
|
1303
|
+
```vue
|
|
1304
|
+
<BaseRemoveButton :aria-label="`Remove header ${i + 1}`" @click="rows.splice(i, 1)" />
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
---
|
|
1308
|
+
|
|
1309
|
+
### BaseStatusPill
|
|
1310
|
+
|
|
1311
|
+
Connection/health status pill: a colored dot (pulsing while `ok`) next to a
|
|
1312
|
+
short label on a tinted background.
|
|
1313
|
+
|
|
1314
|
+
**Props:**
|
|
1315
|
+
|
|
1316
|
+
| Prop | Type | Default | Description |
|
|
1317
|
+
| -------- | -------- | ------- | ----------- |
|
|
1318
|
+
| `status` | `String` | **required** | `'ok'` (emerald, pulsing), `'error'` (red), `'unknown'` (gray). |
|
|
1319
|
+
| `label` | `String` | **required** | Short text next to the dot, e.g. `WireMock Connected`. |
|
|
1320
|
+
|
|
1321
|
+
```vue
|
|
1322
|
+
<BaseStatusPill :status="healthy ? 'ok' : 'error'" :label="healthy ? 'Connected' : 'Disconnected'" />
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
---
|
|
1326
|
+
|
|
1327
|
+
### BaseFileDropzone
|
|
1328
|
+
|
|
1329
|
+
Dashed "click to select a file" upload zone (extracted from WireMate's
|
|
1330
|
+
Postman-import modal). Renders a document-arrow-up icon (overridable via the
|
|
1331
|
+
`#icon` slot), a label line, and an optional dimmed hint line. Clicking opens
|
|
1332
|
+
the native file picker; dragging files onto the zone also works (the border
|
|
1333
|
+
highlights emerald while dragging). The hidden input resets after every
|
|
1334
|
+
selection, so picking the same file twice still emits.
|
|
1335
|
+
|
|
1336
|
+
**Props:**
|
|
1337
|
+
|
|
1338
|
+
| Prop | Type | Default | Description |
|
|
1339
|
+
| ---------- | --------- | ------- | ----------- |
|
|
1340
|
+
| `label` | `String` | **required** | Main line, e.g. `Click to select a Postman collection (.json)`. |
|
|
1341
|
+
| `hint` | `String` | `''` | Dimmed helper line below the label. |
|
|
1342
|
+
| `accept` | `String` | `''` | Forwarded to the input's `accept`. Dropped files are **not** filtered by it. |
|
|
1343
|
+
| `multiple` | `Boolean` | `false` | Allow multi-select; when `false`, a multi-file drop emits only the first file. |
|
|
1344
|
+
| `disabled` | `Boolean` | `false` | Dims the zone and ignores clicks/drops. |
|
|
1345
|
+
|
|
1346
|
+
**Emits:** `files` (`File[]`, never empty).
|
|
1347
|
+
|
|
1348
|
+
**Slots:** `icon` — replaces the default upload icon.
|
|
1349
|
+
|
|
1350
|
+
```vue
|
|
1351
|
+
<BaseFileDropzone
|
|
1352
|
+
accept="application/json,.json"
|
|
1353
|
+
label="Click to select a Postman collection (.json)"
|
|
1354
|
+
hint="Exported from Postman → Export → Collection v2.1"
|
|
1355
|
+
@files="onFiles"
|
|
1356
|
+
/>
|
|
1357
|
+
```
|
|
1358
|
+
|
|
1359
|
+
### BaseCodeBlock
|
|
1360
|
+
|
|
1361
|
+
Themed monospace `<pre>` for JSON payloads, request dumps and code snippets
|
|
1362
|
+
(extracted from WireMate's stub/request detail views). Preserves whitespace
|
|
1363
|
+
verbatim, scrolls both axes, and adapts to the theme. Extra classes (margins
|
|
1364
|
+
etc.) fall through via the normal class merge.
|
|
1365
|
+
|
|
1366
|
+
**Props:**
|
|
1367
|
+
|
|
1368
|
+
| Prop | Type | Default | Description |
|
|
1369
|
+
| ---------------- | -------- | -------- | ----------- |
|
|
1370
|
+
| `code` | `String` | **required** | The raw text to render. |
|
|
1371
|
+
| `variant` | `String` | `'soft'` | `'soft'` = tinted fill, no border (in-card look); `'bordered'` = bordered card fill (standalone look). |
|
|
1372
|
+
| `size` | `String` | `'sm'` | `'sm'` = `text-sm px-5 py-4`; `'xs'` = dense `text-xs p-3`. |
|
|
1373
|
+
| `maxHeightClass` | `String` | `''` | Optional Tailwind max-height utility, e.g. `max-h-96`. |
|
|
1374
|
+
|
|
1375
|
+
```vue
|
|
1376
|
+
<BaseCodeBlock :code="formatJson(response.body)" size="xs" max-height-class="max-h-64" />
|
|
1377
|
+
```
|
|
1378
|
+
|
|
1379
|
+
---
|
|
1380
|
+
|
|
1381
|
+
## Forms & tables
|
|
1382
|
+
|
|
1383
|
+
These components use `dark:` Tailwind variants, so the consuming app must map
|
|
1384
|
+
the `dark` variant to the `.dark` class that `useTheme()` toggles (see
|
|
1385
|
+
[Tailwind setup for consumers](#tailwind-setup-for-consumers)).
|
|
1386
|
+
|
|
1387
|
+
### BaseInput
|
|
1388
|
+
|
|
1389
|
+
Themed text/number input carrying the shared field skin (slate border,
|
|
1390
|
+
`bg-slate-50` / dark `bg-slate-900` surface). Everything else — `placeholder`,
|
|
1391
|
+
`id`, `disabled`, `step`/`min`, extra classes like `font-mono` or
|
|
1392
|
+
`placeholder:*` — falls through via attrs and Vue class merging.
|
|
1393
|
+
|
|
1394
|
+
**Props:**
|
|
1395
|
+
|
|
1396
|
+
| Prop | Type | Default | Description |
|
|
1397
|
+
| ------------ | ------------------ | -------- | ----------- |
|
|
1398
|
+
| `modelValue` | `String \| Number \| null` | `''` | `v-model` value. |
|
|
1399
|
+
| `type` | `String` | `'text'` | Native input type. |
|
|
1400
|
+
| `size` | `String` | `'md'` | `'md'` = `px-3 py-2`, `'sm'` = `px-2 py-1.5`. |
|
|
1401
|
+
| `block` | `Boolean` | `true` | Full-width (`w-full`); set `false` for inline fields. |
|
|
1402
|
+
|
|
1403
|
+
**Emits:** `update:modelValue(value: string)` — always the raw string; parse
|
|
1404
|
+
numbers in the owner.
|
|
1405
|
+
|
|
1406
|
+
```vue
|
|
1407
|
+
<BaseInput v-model="query" placeholder="e.g. AMD or BTC" class="font-mono" />
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
### BaseSelect
|
|
1411
|
+
|
|
1412
|
+
Themed `<select>` sharing BaseInput's field skin. Options come from the
|
|
1413
|
+
default slot so callers keep full control of `<option>` rendering.
|
|
1414
|
+
|
|
1415
|
+
**Props:**
|
|
1416
|
+
|
|
1417
|
+
| Prop | Type | Default | Description |
|
|
1418
|
+
| ------------ | ------------------ | ------- | ----------- |
|
|
1419
|
+
| `modelValue` | `String \| null` | `undefined` | `v-model` value. When left undefined the browser keeps its own default selection. |
|
|
1420
|
+
| `size` | `String` | `'sm'` | `'sm'` = `px-2 py-1.5`, `'md'` = `px-3 py-2`. |
|
|
1421
|
+
| `block` | `Boolean` | `true` | Full-width; set `false` for inline selects. |
|
|
1422
|
+
|
|
1423
|
+
**Slots:** `default` — the `<option>` elements.
|
|
1424
|
+
**Emits:** `update:modelValue(value: string)`.
|
|
1425
|
+
|
|
1426
|
+
```vue
|
|
1427
|
+
<BaseSelect v-model="strategyType">
|
|
1428
|
+
<option v-for="e in catalog" :key="e.type" :value="e.type" :title="e.description">
|
|
1429
|
+
{{ e.label }}
|
|
1430
|
+
</option>
|
|
1431
|
+
</BaseSelect>
|
|
1432
|
+
```
|
|
1433
|
+
|
|
1434
|
+
### BaseDropdown
|
|
1435
|
+
|
|
1436
|
+
Button-style single-select dropdown ("Select Social User ⌄"). Unlike
|
|
1437
|
+
`BaseSelect` (a native `<select>`), this renders a trigger button plus a
|
|
1438
|
+
floating menu, so the closed control shows a placeholder and a chevron that
|
|
1439
|
+
rotates while open — matching the app's filter dropdowns. Selecting a row
|
|
1440
|
+
emits its `value` and closes the menu; Escape and an outside click also close
|
|
1441
|
+
it.
|
|
1442
|
+
|
|
1443
|
+
**Props:**
|
|
1444
|
+
|
|
1445
|
+
| Prop | Type | Default | Description |
|
|
1446
|
+
| ------------- | -------------------------- | ------------ | ----------- |
|
|
1447
|
+
| `options` | `DropdownOption[]` | **required** | `{ value, label, title?, disabled? }` per row. |
|
|
1448
|
+
| `modelValue` | `String \| Number \| null` | `null` | Selected option's `value` (`v-model`). |
|
|
1449
|
+
| `placeholder` | `String` | `'Select'` | Trigger text shown when nothing is selected. |
|
|
1450
|
+
| `size` | `String` | `'md'` | `'md'` = `px-4 py-2.5` (app filter height), `'sm'` = `px-3 py-2`. Ignored when `triggerClass` is set. |
|
|
1451
|
+
| `block` | `Boolean` | `true` | Full-width; set `false` for an inline, content-width dropdown. |
|
|
1452
|
+
| `disabled` | `Boolean` | `false` | Disables the trigger. |
|
|
1453
|
+
| `ariaLabel` | `String` | `''` | Accessible name for the trigger/listbox when there is no visible label. |
|
|
1454
|
+
| `triggerClass`| `String` | `''` | Replaces the trigger's default slate skin entirely (including the `size` padding). |
|
|
1455
|
+
| `chevronClass`| `String` | `'w-5 h-5 text-slate-500 dark:text-slate-400'` | Classes for the chevron icon. |
|
|
1456
|
+
|
|
1457
|
+
**Emits:** `update:modelValue(value)`.
|
|
1458
|
+
|
|
1459
|
+
```vue
|
|
1460
|
+
<BaseDropdown
|
|
1461
|
+
v-model="socialUserId"
|
|
1462
|
+
:options="socialUsers.map((u) => ({ value: u.id, label: u.name }))"
|
|
1463
|
+
placeholder="Select Social User"
|
|
1464
|
+
aria-label="Social user"
|
|
1465
|
+
/>
|
|
1466
|
+
```
|
|
1467
|
+
|
|
1468
|
+
### BaseSegmentedControl
|
|
1469
|
+
|
|
1470
|
+
Segmented button group ("All | Stock | Crypto"). One button per option; the
|
|
1471
|
+
selected one gets the filled treatment and `aria-pressed="true"`.
|
|
1472
|
+
|
|
1473
|
+
**Props:**
|
|
1474
|
+
|
|
1475
|
+
| Prop | Type | Default | Description |
|
|
1476
|
+
| ------------- | ------------------- | -------- | ----------- |
|
|
1477
|
+
| `options` | `SegmentedOption[]` | **required** | `{ value, label, title? }` per button. |
|
|
1478
|
+
| `modelValue` | `String \| Number` | **required** | Selected option's `value` (`v-model`). |
|
|
1479
|
+
| `variant` | `String` | `'base'` | `'base'` (`px-3 py-2`, emerald-500 fill), `'wide'` (`px-4 py-2`, emerald-600 fill), `'toolbar'` (`h-9` uppercase `text-xs` with focus-visible rings). |
|
|
1480
|
+
| `ariaLabel` | `String` | `''` | When set, the wrapper renders `role="group"` + `aria-label`. |
|
|
1481
|
+
| `optionClass` | `Function` | — | `(option, active) => string` override for per-button fill classes (e.g. severity colours); layout stays owned by the variant. |
|
|
1482
|
+
|
|
1483
|
+
**Emits:** `update:modelValue(value)`.
|
|
1484
|
+
|
|
1485
|
+
```vue
|
|
1486
|
+
<BaseSegmentedControl v-model="assetFilter" :options="ASSET_FILTERS" />
|
|
1487
|
+
<BaseSegmentedControl v-model="exchange" :options="EXCHANGES" variant="wide" aria-label="Exchange" />
|
|
1488
|
+
```
|
|
1489
|
+
|
|
1490
|
+
### BaseTable
|
|
1491
|
+
|
|
1492
|
+
Styling shell for data tables — **not** a data grid. Owns the table skin
|
|
1493
|
+
(slate header band, `px-4 py-3` header cells, empty-state row); body rows are
|
|
1494
|
+
the caller's own `<tr>` markup via the default slot. Wrap it yourself for
|
|
1495
|
+
scrolling/card chrome (e.g. a `BaseRow` with `overflow-x-auto`).
|
|
1496
|
+
|
|
1497
|
+
**Props:**
|
|
1498
|
+
|
|
1499
|
+
| Prop | Type | Default | Description |
|
|
1500
|
+
| ----------- | --------------- | ------------ | ----------- |
|
|
1501
|
+
| `columns` | `TableColumn[]` | **required** | `{ label, align? }`; `align: 'right'` right-aligns the header cell. |
|
|
1502
|
+
| `empty` | `Boolean` | `false` | True renders the empty-state row spanning every column. |
|
|
1503
|
+
| `emptyText` | `String` | `'No rows.'` | Fallback empty-state text. |
|
|
1504
|
+
|
|
1505
|
+
**Slots:** `default` — the `<tr>` rows; `empty` — custom empty-state content.
|
|
1506
|
+
|
|
1507
|
+
```vue
|
|
1508
|
+
<BaseTable :columns="COLUMNS" :empty="rows.length === 0">
|
|
1509
|
+
<template #empty>No trades match your filters.</template>
|
|
1510
|
+
<tr v-for="row in rows" :key="row.id" class="border-t border-slate-200 dark:border-slate-700">
|
|
1511
|
+
…
|
|
1512
|
+
</tr>
|
|
1513
|
+
</BaseTable>
|
|
1514
|
+
```
|
|
1515
|
+
|
|
1516
|
+
### BaseSpecFields
|
|
1517
|
+
|
|
1518
|
+
Spec-driven form fields: renders a select / checkbox / number input per
|
|
1519
|
+
`SpecField`, with labels and help text, in a responsive two-column grid. Feed
|
|
1520
|
+
it a backend-described catalogue and every form editing those values stays in
|
|
1521
|
+
lockstep. Never mutates `params` — every edit is emitted as `(key, value)`
|
|
1522
|
+
and the owner writes it back into its own state.
|
|
1523
|
+
|
|
1524
|
+
**Props:**
|
|
1525
|
+
|
|
1526
|
+
| Prop | Type | Default | Description |
|
|
1527
|
+
| -------- | -------------------------------- | ------------ | ----------- |
|
|
1528
|
+
| `specs` | `SpecField[]` | **required** | `{ key, label, type: 'decimal' \| 'integer' \| 'boolean' \| 'select', default?, options?, step?, min?, help? }` (optional members are nullable). |
|
|
1529
|
+
| `params` | `Record<string, SpecFieldValue>` | **required** | Current values keyed by `spec.key`. |
|
|
1530
|
+
|
|
1531
|
+
**Slots:** `after` (`{ spec }`) — extra content under each field (e.g. a live
|
|
1532
|
+
preview attached to one key).
|
|
1533
|
+
**Emits:** `update(key: string, value: SpecFieldValue)` — numbers are parsed
|
|
1534
|
+
(`parseFloat`); unparseable input passes through raw so the owner's
|
|
1535
|
+
validation can catch it.
|
|
1536
|
+
|
|
1537
|
+
```vue
|
|
1538
|
+
<BaseSpecFields :specs="entry.params" :params="form.params"
|
|
1539
|
+
@update="(key, value) => (form.params[key] = value)" />
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
### BaseStatBreakdown
|
|
1543
|
+
|
|
1544
|
+
Compact per-item breakdown meant to sit under a summary/stat card (pairs with
|
|
1545
|
+
[`EarningsCard`](#earningscard)). Each item renders on its own line — label
|
|
1546
|
+
left, value right in monospace. A `null`/`undefined` value (a source that is
|
|
1547
|
+
unconfigured, unreachable, or has no matching rows) shows an em-dash rather
|
|
1548
|
+
than a misleading 0. With `signed`, values gain an explicit "+" and are
|
|
1549
|
+
coloured green/red by sign (same `"-$3.00"` / `"+$5.00"` shape as `fmtUsd`).
|
|
1550
|
+
|
|
1551
|
+
**Props:**
|
|
1552
|
+
|
|
1553
|
+
| Prop | Type | Default | Description |
|
|
1554
|
+
| ---------- | --------------------- | ------------ | ----------- |
|
|
1555
|
+
| `items` | `StatBreakdownItem[]` | **required** | `{ label, value }` per row. |
|
|
1556
|
+
| `currency` | `String` | `''` | Currency symbol placed after the sign, e.g. `'$'`. |
|
|
1557
|
+
| `decimals` | `Number` | `2` | Fraction digits shown for each value. |
|
|
1558
|
+
| `signed` | `Boolean` | `false` | Show an explicit "+" on non-negative values and colour rows green/red by sign. |
|
|
1559
|
+
|
|
1560
|
+
```ts
|
|
1561
|
+
import type { StatBreakdownItem } from 'mgv-backoffice'
|
|
1562
|
+
|
|
1563
|
+
interface StatBreakdownItem {
|
|
1564
|
+
label: string // row label rendered on the left
|
|
1565
|
+
value: number | null | undefined // null/undefined renders as an em-dash
|
|
1566
|
+
}
|
|
1567
|
+
```
|
|
1568
|
+
|
|
1569
|
+
```vue
|
|
1570
|
+
<EarningsCard title="TOTAL P&L" :amount="totalPnl" signed />
|
|
1571
|
+
<BaseStatBreakdown
|
|
1572
|
+
:items="[
|
|
1573
|
+
{ label: 'Alpaca', value: 42.5 },
|
|
1574
|
+
{ label: 'Binance', value: -3.1 },
|
|
1575
|
+
{ label: 'Kraken', value: null },
|
|
1576
|
+
]"
|
|
1577
|
+
currency="$"
|
|
1578
|
+
signed
|
|
1579
|
+
/>
|
|
1580
|
+
```
|
|
1581
|
+
|
|
1582
|
+
### BaseFilterChip
|
|
1583
|
+
|
|
1584
|
+
Colour-coded toggleable filter chip — one-click event/category filters above
|
|
1585
|
+
a data feed. Idle renders a tinted border/background in the semantic colour;
|
|
1586
|
+
active renders a solid fill with white text (`aria-pressed` reflects the
|
|
1587
|
+
state). Layout classes (`h-9 flex-1`, …) pass through the class attribute;
|
|
1588
|
+
click handlers bind natively on the component.
|
|
1589
|
+
|
|
1590
|
+
**Props:**
|
|
1591
|
+
|
|
1592
|
+
| Prop | Type | Default | Description |
|
|
1593
|
+
| ---------- | --------- | --------- | ----------- |
|
|
1594
|
+
| `label` | `String` | `''` | Chip text; the default slot overrides it. |
|
|
1595
|
+
| `color` | `'emerald' \| 'sky' \| 'amber' \| 'red' \| 'slate'` | `'slate'` | Semantic colour of the idle tint and active fill. |
|
|
1596
|
+
| `active` | `Boolean` | `false` | Whether the chip's filter is applied (solid fill). |
|
|
1597
|
+
| `disabled` | `Boolean` | `false` | Greys out + blocks the click. |
|
|
1598
|
+
| `title` | `String` | — | Native tooltip. |
|
|
1599
|
+
|
|
1600
|
+
```vue
|
|
1601
|
+
<BaseFilterChip
|
|
1602
|
+
v-for="f in filters"
|
|
1603
|
+
:key="f.key"
|
|
1604
|
+
class="h-9 flex-1"
|
|
1605
|
+
:color="f.color"
|
|
1606
|
+
:active="isActive(f)"
|
|
1607
|
+
:title="f.title"
|
|
1608
|
+
@click="toggle(f)"
|
|
1609
|
+
>{{ f.label }}</BaseFilterChip>
|
|
1610
|
+
```
|
|
1611
|
+
|
|
1612
|
+
### BaseCredentialsForm
|
|
1613
|
+
|
|
1614
|
+
One service's API-credentials card: key id + secret + base/data URLs, with
|
|
1615
|
+
the has-secret handling (placeholder dots, blank-keeps-stored-secret), the
|
|
1616
|
+
save-validation ladder and a saving spinner. Load/save results are EMITTED —
|
|
1617
|
+
the parent owns toasts / error banners. Exposes `load()` so a parent Reload
|
|
1618
|
+
button can re-pull several cards in parallel.
|
|
1619
|
+
|
|
1620
|
+
**Props:** `title` + `idPrefix` + `fetchFn: () => Promise<CredentialsView>` +
|
|
1621
|
+
`updateFn: (body: CredentialsUpdate) => Promise<CredentialsView>` +
|
|
1622
|
+
`defaults: { baseUrl, dataUrl }` (required); `subtitle`, `keyLabel`,
|
|
1623
|
+
`keyPlaceholder`, `secretLabel`, `secretPlaceholder`, `secretSetHint`,
|
|
1624
|
+
`permissionsHint`, `requiredKeyMessage`, `requiredSecretMessage`,
|
|
1625
|
+
`savedMessage`, `saveLabel` (optional copy overrides).
|
|
1626
|
+
|
|
1627
|
+
**Slots:** `no-secret-hint` — rich help while no secret is stored;
|
|
1628
|
+
`base-url-extra` (`{ form }`) — extras under the Base URL field (e.g.
|
|
1629
|
+
live/paper shortcut buttons that write into the form); `footer` — extra
|
|
1630
|
+
content at the card's bottom.
|
|
1631
|
+
|
|
1632
|
+
**Emits:** `saved(message)`, `error(message)`, `load-error(message)`.
|
|
1633
|
+
|
|
1634
|
+
```vue
|
|
1635
|
+
<BaseCredentialsForm
|
|
1636
|
+
ref="card"
|
|
1637
|
+
title="Alpaca API"
|
|
1638
|
+
id-prefix="alpaca"
|
|
1639
|
+
:fetch-fn="fetchAlpaca"
|
|
1640
|
+
:update-fn="updateAlpaca"
|
|
1641
|
+
:defaults="{ baseUrl: LIVE_BASE, dataUrl: DATA_URL }"
|
|
1642
|
+
@saved="onSaved"
|
|
1643
|
+
@error="onError"
|
|
1644
|
+
@load-error="onLoadError"
|
|
1645
|
+
/>
|
|
1646
|
+
```
|
|
1647
|
+
|
|
1648
|
+
---
|
|
1649
|
+
|
|
1650
|
+
### BasePillPickerModal
|
|
1651
|
+
|
|
1652
|
+
"Pick one of many" modal: every item rendered as a clickable pill, narrowed
|
|
1653
|
+
by a free-text filter and an optional segmented group toggle. Clicking a pill
|
|
1654
|
+
emits `pick` with the item; backdrop / Escape / the footer Close emit `close`.
|
|
1655
|
+
Narrowing state lives inside, so a `v-if`-mounted instance always opens fresh.
|
|
1656
|
+
|
|
1657
|
+
**Props:** `title` + `items: PillPickerItem[]` (required);
|
|
1658
|
+
`groups?: SegmentedOption<string>[]` (renders the group toggle with an
|
|
1659
|
+
`allLabel` option prepended, narrowing by each item's `group`); `icon?`
|
|
1660
|
+
(defaults to the magnifying glass), `subtitle?`, `searchPlaceholder`,
|
|
1661
|
+
`emptyMessage`, `noMatchMessage`, `mono` (mono font for the filter input and
|
|
1662
|
+
pills — symbols, codes, ids), `maxWidthClass` (default `max-w-2xl`),
|
|
1663
|
+
`closeText`, `groupAriaLabel`, `allLabel`.
|
|
1664
|
+
|
|
1665
|
+
**Emits:** `pick(item: PillPickerItem)`, `close`.
|
|
1666
|
+
|
|
1667
|
+
```ts
|
|
1668
|
+
interface PillPickerItem {
|
|
1669
|
+
id: string // unique key; identifies the pick
|
|
1670
|
+
label: string // pill text; what the filter matches
|
|
1671
|
+
group?: string // segmented-toggle bucket
|
|
1672
|
+
title?: string // pill tooltip
|
|
1673
|
+
}
|
|
1674
|
+
```
|
|
1675
|
+
|
|
1676
|
+
```vue
|
|
1677
|
+
<BasePillPickerModal
|
|
1678
|
+
v-if="open"
|
|
1679
|
+
title="Symbols"
|
|
1680
|
+
:items="symbols.map(s => ({ id: s.id, label: s.symbol, group: s.assetClass }))"
|
|
1681
|
+
:groups="[{ value: 'STOCK', label: 'STOCK' }, { value: 'CRYPTO', label: 'CRYPTO' }]"
|
|
1682
|
+
mono
|
|
1683
|
+
@pick="apply"
|
|
1684
|
+
@close="open = false"
|
|
1685
|
+
/>
|
|
1686
|
+
```
|
|
1687
|
+
|
|
1688
|
+
---
|
|
1689
|
+
|
|
1690
|
+
### BaseBarDistribution
|
|
1691
|
+
|
|
1692
|
+
Compact value-distribution chart: one thin rounded bar per distinct
|
|
1693
|
+
value, count labelled on top and the value underneath — scrolls
|
|
1694
|
+
sideways when there are many bars. Pure Tailwind, no chart library.
|
|
1695
|
+
Extracted from TradeAutomation's variant-stats modal.
|
|
1696
|
+
|
|
1697
|
+
**Props:**
|
|
1698
|
+
|
|
1699
|
+
| Prop | Type | Default | Description |
|
|
1700
|
+
| -------------- | -------- | ---------------------- | ----------- |
|
|
1701
|
+
| `bars` | `Array` | **required** | `DistributionBar[]` — `{ label, count }` per bar, in display order (sort ascending for numeric values). |
|
|
1702
|
+
| `ariaLabel` | `String` | `'Value distribution'` | Accessible description of the chart. |
|
|
1703
|
+
| `countNoun` | `String` | `'item'` | Noun for each bar's tooltip count, e.g. `'variant'` → "3 variants". |
|
|
1704
|
+
| `titlePrefix` | `String` | `''` | Tooltip prefix before the value, e.g. the field name. |
|
|
1705
|
+
| `maxBarHeight` | `Number` | `56` | Height of the tallest bar, in px. |
|
|
1706
|
+
| `barClass` | `String` | emerald fill | Tailwind classes for the bar fill. |
|
|
1707
|
+
|
|
1708
|
+
```vue
|
|
1709
|
+
<BaseBarDistribution
|
|
1710
|
+
:bars="[{ label: '0.5', count: 1 }, { label: '1', count: 4 }]"
|
|
1711
|
+
aria-label="Distribution of Take profit across variants"
|
|
1712
|
+
title-prefix="Take profit"
|
|
1713
|
+
count-noun="variant"
|
|
1714
|
+
/>
|
|
1715
|
+
```
|
|
1716
|
+
|
|
1717
|
+
---
|
|
1718
|
+
|
|
1719
|
+
## Composables
|
|
1720
|
+
|
|
1721
|
+
```ts
|
|
1722
|
+
import {
|
|
1723
|
+
initTheme,
|
|
1724
|
+
useTheme,
|
|
1725
|
+
useThemeClasses,
|
|
1726
|
+
useEscapeKey,
|
|
1727
|
+
useDebouncedRef,
|
|
1728
|
+
useToast,
|
|
1729
|
+
useMobileSidebar,
|
|
1730
|
+
useSidebarCollapse,
|
|
1731
|
+
useNotifications,
|
|
1732
|
+
useQueryParamSync,
|
|
1733
|
+
useFieldClasses,
|
|
1734
|
+
usePolling,
|
|
1735
|
+
} from 'mgv-backoffice'
|
|
1736
|
+
import type {
|
|
1737
|
+
UseThemeOptions,
|
|
1738
|
+
UseSidebarCollapseOptions,
|
|
1739
|
+
UsePollingOptions,
|
|
1740
|
+
} from 'mgv-backoffice'
|
|
1741
|
+
```
|
|
1742
|
+
|
|
1743
|
+
| Composable | Purpose |
|
|
1744
|
+
| ---------- | ------- |
|
|
1745
|
+
| `initTheme({ storageKey? })` | Explicitly initialize the theme singleton. Call in your app entry point **before mounting** when you need a custom storage key — library components call `useTheme()` internally, so a component mounting first would otherwise lock in the default key (a dev-mode warning fires if that happens). |
|
|
1746
|
+
| `useTheme({ storageKey? })` | Singleton dark/light controller. Toggles `<html class="dark">` and persists via localStorage (default key `'mgv-theme'`). Prefer `initTheme` at app entry for custom keys. |
|
|
1747
|
+
| `useThemeClasses()` | Named Tailwind class roles for dark/light (card, border, primaryText, mutedText, dimText, input, ghostButton, emeraldText, redText, …). Since 1.34.0 returns a `reactive` object of plain strings — bind `t.card` directly, never `t.card.value` (the old ComputedRef shape leaked ref internals into `:class` bindings). |
|
|
1748
|
+
| `useEscapeKey(handler)` | Component-scoped Escape key listener. |
|
|
1749
|
+
| `useDebouncedRef(source, delay?)` | Debounced mirror of a ref. Timer cleared on scope dispose. |
|
|
1750
|
+
| `useToast(durationMs?)` | Per-component toast state: `{ showToast, toastMessage, toastType, showToastMessage }`. `showToastMessage` also accepts a per-call duration override. |
|
|
1751
|
+
| `useMobileSidebar()` | Singleton state shared between `BaseSidebar` and `BaseAppLayout` for the off-canvas open/closed flag. |
|
|
1752
|
+
| `useSidebarCollapse({ storageKey? })` | Singleton collapsed/expanded state for the desktop sidebar rail, shared between `BaseSidebar` and `BaseAppLayout` and persisted to localStorage (default key `'mgv-sidebar-collapsed'`). |
|
|
1753
|
+
| `useNotifications()` | Singleton notification state shared by the sidebar bell and `BaseNotificationPanel`: `{ notifications, unreadCount, open, openPanel, closePanel, togglePanel, setNotifications, add, remove, markRead, markAllRead, clear }`. |
|
|
1754
|
+
| `useQueryParamSync()` | URL-query mirroring for filterable views: `{ qparam(name), qenum(name, allowed, fallback), replaceQuery(next) }`. Read filters from the query string once on setup, write changes back with `router.replace` (no-op when unchanged) so filtered views stay shareable without polluting history. |
|
|
1755
|
+
| `useFieldClasses()` | Shared form-field class strings for the gray/emerald form skin: `{ label, input, requiredInput(value) }`. `requiredInput` returns a red border+ring skin while the value is empty and the standard skin otherwise. |
|
|
1756
|
+
| `usePolling(fn, intervalMs, { immediate?, pauseWhenHidden? })` | Visibility-gated polling loop bound to the component lifecycle: starts on mount, stops on unmount, pauses while the tab is hidden and refreshes + resumes on return to visible (both default on). Pass `intervalMs: null` for refresh-only mode (run on mount + each return-to-visible, no timer). Returns `{ start, stop, active }`. A loop stopped via `stop()` stays stopped across hide/show cycles (since 1.36.0) — only a visibility-paused loop auto-resumes. Catch errors inside `fn` — the loop never swallows rejections. |
|
|
1757
|
+
|
|
1758
|
+
---
|
|
1759
|
+
|
|
1760
|
+
## Typography
|
|
1761
|
+
|
|
1762
|
+
Since 1.33.0 the library ships the shared brand typography: the stylesheet
|
|
1763
|
+
loads **Fira Sans** (UI text) and **Fira Code** (numerals/data) from Google
|
|
1764
|
+
Fonts via `@import`, registers them as the Tailwind `--font-sans` /
|
|
1765
|
+
`--font-mono` theme defaults, and applies `font-family: var(--font-sans)` to
|
|
1766
|
+
`body`. Consumers get the fonts just by importing the lib CSS — remove any
|
|
1767
|
+
app-local Google Fonts `<link rel="stylesheet">` and `--font-sans`/`--font-mono`
|
|
1768
|
+
overrides. Keep (or add) the preconnect hints in `index.html` for a faster
|
|
1769
|
+
first paint:
|
|
1770
|
+
|
|
1771
|
+
```html
|
|
1772
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
1773
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
1774
|
+
```
|
|
1775
|
+
|
|
1776
|
+
## Tailwind setup for consumers
|
|
1777
|
+
|
|
1778
|
+
The lib's components rely on Tailwind utility classes (including dark-mode
|
|
1779
|
+
variants). Consumers should add the lib's `dist` output to their Tailwind
|
|
1780
|
+
`content` paths so the JIT can see the class names:
|
|
1781
|
+
|
|
1782
|
+
```js
|
|
1783
|
+
// tailwind.config.js
|
|
1784
|
+
export default {
|
|
1785
|
+
content: [
|
|
1786
|
+
'./index.html',
|
|
1787
|
+
'./src/**/*.{vue,ts}',
|
|
1788
|
+
'./node_modules/mgv-backoffice/dist/**/*.{js,mjs,cjs,vue}',
|
|
1789
|
+
],
|
|
1790
|
+
}
|
|
1791
|
+
```
|
|
1792
|
+
|
|
1793
|
+
The legacy `tailwind.safelist.js` only covers the v1 components; the
|
|
1794
|
+
recommended path for v4+ is the `content` glob above.
|