@birdycom/omniaui-vue 0.12.5 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blocks/OcBlockContactForm.vue.d.ts +71 -0
- package/dist/blocks/OcBlockDataTable.vue.d.ts +1 -1
- package/dist/blocks/OcBlockProfileHeader.vue.d.ts +5 -3
- package/dist/components/OcAvatarPicker.vue.d.ts +21 -0
- package/dist/components/OcBarcode.vue.d.ts +12 -0
- package/dist/components/OcBrandMark.vue.d.ts +23 -0
- package/dist/components/OcBreadcrumb.vue.d.ts +7 -1
- package/dist/components/OcButton.vue.d.ts +6 -3
- package/dist/components/OcDataTable.vue.d.ts +68 -1
- package/dist/components/OcDataTableName.vue.d.ts +20 -0
- package/dist/components/OcDateField.vue.d.ts +19 -0
- package/dist/components/OcEmojiPicker.vue.d.ts +1 -1
- package/dist/components/OcFeatureCard.vue.d.ts +23 -0
- package/dist/components/OcFormSection.vue.d.ts +25 -0
- package/dist/components/OcGallery.vue.d.ts +17 -0
- package/dist/components/OcGlass.vue.d.ts +2 -1
- package/dist/components/OcGlassBar.vue.d.ts +32 -0
- package/dist/components/OcLazyImage.vue.d.ts +24 -0
- package/dist/components/OcLinkCard.vue.d.ts +22 -0
- package/dist/components/OcMeterRow.vue.d.ts +9 -0
- package/dist/components/{OcColumnMenu.vue.d.ts → OcNameCard.vue.d.ts} +15 -16
- package/dist/components/OcPanel.vue.d.ts +16 -0
- package/dist/components/OcPrice.vue.d.ts +20 -0
- package/dist/components/OcQuantity.vue.d.ts +24 -0
- package/dist/components/OcRating.vue.d.ts +11 -0
- package/dist/components/OcRatingInput.vue.d.ts +18 -0
- package/dist/components/OcResult.vue.d.ts +23 -0
- package/dist/components/OcRibbon.vue.d.ts +22 -0
- package/dist/components/OcSpecList.vue.d.ts +15 -0
- package/dist/components/OcStock.vue.d.ts +20 -0
- package/dist/index.d.ts +29 -3
- package/dist/omniaui-vue.js +4994 -3338
- package/dist/omniaui-vue.js.map +1 -1
- package/manifest.json +138 -6
- package/package.json +1 -1
- package/src/blocks/OcBlockContactForm.vue +146 -0
- package/src/blocks/OcBlockDataTable.vue +1 -1
- package/src/blocks/OcBlockProfileHeader.vue +15 -4
- package/src/components/OcAttachmentList.vue +1 -1
- package/src/components/OcAvatarPicker.vue +34 -0
- package/src/components/OcBarcode.vue +23 -0
- package/src/components/OcBrandMark.vue +15 -0
- package/src/components/OcBreadcrumb.vue +14 -5
- package/src/components/OcButton.vue +10 -2
- package/src/components/OcChatComposer.vue +1 -1
- package/src/components/OcDataTable.vue +100 -25
- package/src/components/OcDataTableName.vue +18 -0
- package/src/components/OcDateField.vue +23 -0
- package/src/components/OcEmojiPicker.vue +2 -2
- package/src/components/OcFeatureCard.vue +15 -0
- package/src/components/OcFormSection.vue +19 -0
- package/src/components/OcGallery.vue +27 -0
- package/src/components/OcGlass.vue +4 -4
- package/src/components/OcGlassBar.vue +17 -0
- package/src/components/OcLazyImage.vue +17 -0
- package/src/components/OcLinkCard.vue +16 -0
- package/src/components/OcMessageList.vue +6 -6
- package/src/components/OcMessageThread.vue +11 -11
- package/src/components/OcMeterRow.vue +18 -0
- package/src/components/OcNameCard.vue +28 -0
- package/src/components/OcPanel.vue +9 -0
- package/src/components/OcPrice.vue +30 -0
- package/src/components/OcProse.vue +3 -3
- package/src/components/OcQuantity.vue +21 -0
- package/src/components/OcRating.vue +17 -0
- package/src/components/OcRatingInput.vue +17 -0
- package/src/components/OcResult.vue +16 -0
- package/src/components/OcRibbon.vue +9 -0
- package/src/components/OcSpecList.vue +17 -0
- package/src/components/OcStock.vue +13 -0
- package/src/index.ts +74 -4
- package/src/components/OcColumnMenu.vue +0 -50
package/manifest.json
CHANGED
|
@@ -84,6 +84,12 @@
|
|
|
84
84
|
"file": "src/components/OcAvatarGroup.vue",
|
|
85
85
|
"snippet": "@birdycom/omniaui/snippets/avatar.html"
|
|
86
86
|
},
|
|
87
|
+
{
|
|
88
|
+
"name": "OcAvatarPicker",
|
|
89
|
+
"kind": "component",
|
|
90
|
+
"file": "src/components/OcAvatarPicker.vue",
|
|
91
|
+
"snippet": "@birdycom/omniaui/snippets/avatar-picker.html"
|
|
92
|
+
},
|
|
87
93
|
{
|
|
88
94
|
"name": "OcBackLink",
|
|
89
95
|
"kind": "component",
|
|
@@ -96,12 +102,24 @@
|
|
|
96
102
|
"file": "src/components/OcBadge.vue",
|
|
97
103
|
"snippet": "@birdycom/omniaui/snippets/badge.html"
|
|
98
104
|
},
|
|
105
|
+
{
|
|
106
|
+
"name": "OcBarcode",
|
|
107
|
+
"kind": "component",
|
|
108
|
+
"file": "src/components/OcBarcode.vue",
|
|
109
|
+
"snippet": "@birdycom/omniaui/snippets/barcode.html"
|
|
110
|
+
},
|
|
99
111
|
{
|
|
100
112
|
"name": "OcBarcodeInput",
|
|
101
113
|
"kind": "component",
|
|
102
114
|
"file": "src/components/OcBarcodeInput.vue",
|
|
103
115
|
"snippet": "@birdycom/omniaui/snippets/barcode-input.html"
|
|
104
116
|
},
|
|
117
|
+
{
|
|
118
|
+
"name": "OcBrandMark",
|
|
119
|
+
"kind": "component",
|
|
120
|
+
"file": "src/components/OcBrandMark.vue",
|
|
121
|
+
"snippet": "@birdycom/omniaui/snippets/brand-mark.html"
|
|
122
|
+
},
|
|
105
123
|
{
|
|
106
124
|
"name": "OcBreadcrumb",
|
|
107
125
|
"kind": "component",
|
|
@@ -192,12 +210,6 @@
|
|
|
192
210
|
"file": "src/components/OcColorSelect.vue",
|
|
193
211
|
"snippet": "@birdycom/omniaui/snippets/color-select.html"
|
|
194
212
|
},
|
|
195
|
-
{
|
|
196
|
-
"name": "OcColumnMenu",
|
|
197
|
-
"kind": "component",
|
|
198
|
-
"file": "src/components/OcColumnMenu.vue",
|
|
199
|
-
"snippet": "@birdycom/omniaui/snippets/column-menu.html"
|
|
200
|
-
},
|
|
201
213
|
{
|
|
202
214
|
"name": "OcCommand",
|
|
203
215
|
"kind": "component",
|
|
@@ -234,6 +246,18 @@
|
|
|
234
246
|
"file": "src/components/OcDataTable.vue",
|
|
235
247
|
"snippet": "@birdycom/omniaui/snippets/data-table.html"
|
|
236
248
|
},
|
|
249
|
+
{
|
|
250
|
+
"name": "OcDataTableName",
|
|
251
|
+
"kind": "component",
|
|
252
|
+
"file": "src/components/OcDataTableName.vue",
|
|
253
|
+
"snippet": null
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"name": "OcDateField",
|
|
257
|
+
"kind": "component",
|
|
258
|
+
"file": "src/components/OcDateField.vue",
|
|
259
|
+
"snippet": "@birdycom/omniaui/snippets/date-field.html"
|
|
260
|
+
},
|
|
237
261
|
{
|
|
238
262
|
"name": "OcDecimalInput",
|
|
239
263
|
"kind": "component",
|
|
@@ -306,6 +330,12 @@
|
|
|
306
330
|
"file": "src/components/OcFactList.vue",
|
|
307
331
|
"snippet": "@birdycom/omniaui/snippets/fact-list.html"
|
|
308
332
|
},
|
|
333
|
+
{
|
|
334
|
+
"name": "OcFeatureCard",
|
|
335
|
+
"kind": "component",
|
|
336
|
+
"file": "src/components/OcFeatureCard.vue",
|
|
337
|
+
"snippet": "@birdycom/omniaui/snippets/feature-card.html"
|
|
338
|
+
},
|
|
309
339
|
{
|
|
310
340
|
"name": "OcFilterCounts",
|
|
311
341
|
"kind": "component",
|
|
@@ -324,12 +354,30 @@
|
|
|
324
354
|
"file": "src/components/OcFormField.vue",
|
|
325
355
|
"snippet": "@birdycom/omniaui/snippets/form-field.html"
|
|
326
356
|
},
|
|
357
|
+
{
|
|
358
|
+
"name": "OcFormSection",
|
|
359
|
+
"kind": "component",
|
|
360
|
+
"file": "src/components/OcFormSection.vue",
|
|
361
|
+
"snippet": "@birdycom/omniaui/snippets/form-section.html"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"name": "OcGallery",
|
|
365
|
+
"kind": "component",
|
|
366
|
+
"file": "src/components/OcGallery.vue",
|
|
367
|
+
"snippet": "@birdycom/omniaui/snippets/gallery.html"
|
|
368
|
+
},
|
|
327
369
|
{
|
|
328
370
|
"name": "OcGlass",
|
|
329
371
|
"kind": "component",
|
|
330
372
|
"file": "src/components/OcGlass.vue",
|
|
331
373
|
"snippet": "@birdycom/omniaui/snippets/glass.html"
|
|
332
374
|
},
|
|
375
|
+
{
|
|
376
|
+
"name": "OcGlassBar",
|
|
377
|
+
"kind": "component",
|
|
378
|
+
"file": "src/components/OcGlassBar.vue",
|
|
379
|
+
"snippet": null
|
|
380
|
+
},
|
|
333
381
|
{
|
|
334
382
|
"name": "OcGlassPanel",
|
|
335
383
|
"kind": "component",
|
|
@@ -420,6 +468,12 @@
|
|
|
420
468
|
"file": "src/components/OcLauncherCard.vue",
|
|
421
469
|
"snippet": "@birdycom/omniaui/snippets/launcher-card.html"
|
|
422
470
|
},
|
|
471
|
+
{
|
|
472
|
+
"name": "OcLazyImage",
|
|
473
|
+
"kind": "component",
|
|
474
|
+
"file": "src/components/OcLazyImage.vue",
|
|
475
|
+
"snippet": null
|
|
476
|
+
},
|
|
423
477
|
{
|
|
424
478
|
"name": "OcLightbox",
|
|
425
479
|
"kind": "component",
|
|
@@ -432,6 +486,12 @@
|
|
|
432
486
|
"file": "src/components/OcLinkArrow.vue",
|
|
433
487
|
"snippet": "@birdycom/omniaui/snippets/link-arrow.html"
|
|
434
488
|
},
|
|
489
|
+
{
|
|
490
|
+
"name": "OcLinkCard",
|
|
491
|
+
"kind": "component",
|
|
492
|
+
"file": "src/components/OcLinkCard.vue",
|
|
493
|
+
"snippet": null
|
|
494
|
+
},
|
|
435
495
|
{
|
|
436
496
|
"name": "OcLinkRow",
|
|
437
497
|
"kind": "component",
|
|
@@ -492,12 +552,24 @@
|
|
|
492
552
|
"file": "src/components/OcMessageThread.vue",
|
|
493
553
|
"snippet": "@birdycom/omniaui/snippets/message-thread.html"
|
|
494
554
|
},
|
|
555
|
+
{
|
|
556
|
+
"name": "OcMeterRow",
|
|
557
|
+
"kind": "component",
|
|
558
|
+
"file": "src/components/OcMeterRow.vue",
|
|
559
|
+
"snippet": "@birdycom/omniaui/snippets/meter-row.html"
|
|
560
|
+
},
|
|
495
561
|
{
|
|
496
562
|
"name": "OcModal",
|
|
497
563
|
"kind": "component",
|
|
498
564
|
"file": "src/components/OcModal.vue",
|
|
499
565
|
"snippet": "@birdycom/omniaui/snippets/modal.html"
|
|
500
566
|
},
|
|
567
|
+
{
|
|
568
|
+
"name": "OcNameCard",
|
|
569
|
+
"kind": "component",
|
|
570
|
+
"file": "src/components/OcNameCard.vue",
|
|
571
|
+
"snippet": "@birdycom/omniaui/snippets/name-card.html"
|
|
572
|
+
},
|
|
501
573
|
{
|
|
502
574
|
"name": "OcNavmenu",
|
|
503
575
|
"kind": "component",
|
|
@@ -540,6 +612,12 @@
|
|
|
540
612
|
"file": "src/components/OcPagination.vue",
|
|
541
613
|
"snippet": "@birdycom/omniaui/snippets/pagination.html"
|
|
542
614
|
},
|
|
615
|
+
{
|
|
616
|
+
"name": "OcPanel",
|
|
617
|
+
"kind": "component",
|
|
618
|
+
"file": "src/components/OcPanel.vue",
|
|
619
|
+
"snippet": "@birdycom/omniaui/snippets/panel.html"
|
|
620
|
+
},
|
|
543
621
|
{
|
|
544
622
|
"name": "OcPasswordInput",
|
|
545
623
|
"kind": "component",
|
|
@@ -564,6 +642,12 @@
|
|
|
564
642
|
"file": "src/components/OcPopover.vue",
|
|
565
643
|
"snippet": "@birdycom/omniaui/snippets/popover.html"
|
|
566
644
|
},
|
|
645
|
+
{
|
|
646
|
+
"name": "OcPrice",
|
|
647
|
+
"kind": "component",
|
|
648
|
+
"file": "src/components/OcPrice.vue",
|
|
649
|
+
"snippet": "@birdycom/omniaui/snippets/price.html"
|
|
650
|
+
},
|
|
567
651
|
{
|
|
568
652
|
"name": "OcPriceCard",
|
|
569
653
|
"kind": "component",
|
|
@@ -594,6 +678,12 @@
|
|
|
594
678
|
"file": "src/components/OcProse.vue",
|
|
595
679
|
"snippet": "@birdycom/omniaui/snippets/prose.html"
|
|
596
680
|
},
|
|
681
|
+
{
|
|
682
|
+
"name": "OcQuantity",
|
|
683
|
+
"kind": "component",
|
|
684
|
+
"file": "src/components/OcQuantity.vue",
|
|
685
|
+
"snippet": "@birdycom/omniaui/snippets/quantity.html"
|
|
686
|
+
},
|
|
597
687
|
{
|
|
598
688
|
"name": "OcRadio",
|
|
599
689
|
"kind": "component",
|
|
@@ -606,12 +696,36 @@
|
|
|
606
696
|
"file": "src/components/OcRadioGroup.vue",
|
|
607
697
|
"snippet": null
|
|
608
698
|
},
|
|
699
|
+
{
|
|
700
|
+
"name": "OcRating",
|
|
701
|
+
"kind": "component",
|
|
702
|
+
"file": "src/components/OcRating.vue",
|
|
703
|
+
"snippet": "@birdycom/omniaui/snippets/rating.html"
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
"name": "OcRatingInput",
|
|
707
|
+
"kind": "component",
|
|
708
|
+
"file": "src/components/OcRatingInput.vue",
|
|
709
|
+
"snippet": "@birdycom/omniaui/snippets/rating.html"
|
|
710
|
+
},
|
|
609
711
|
{
|
|
610
712
|
"name": "OcRecoveryCodes",
|
|
611
713
|
"kind": "component",
|
|
612
714
|
"file": "src/components/OcRecoveryCodes.vue",
|
|
613
715
|
"snippet": "@birdycom/omniaui/snippets/recovery-codes.html"
|
|
614
716
|
},
|
|
717
|
+
{
|
|
718
|
+
"name": "OcResult",
|
|
719
|
+
"kind": "component",
|
|
720
|
+
"file": "src/components/OcResult.vue",
|
|
721
|
+
"snippet": "@birdycom/omniaui/snippets/result.html"
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
"name": "OcRibbon",
|
|
725
|
+
"kind": "component",
|
|
726
|
+
"file": "src/components/OcRibbon.vue",
|
|
727
|
+
"snippet": "@birdycom/omniaui/snippets/ribbon.html"
|
|
728
|
+
},
|
|
615
729
|
{
|
|
616
730
|
"name": "OcScrollArea",
|
|
617
731
|
"kind": "component",
|
|
@@ -702,6 +816,12 @@
|
|
|
702
816
|
"file": "src/components/OcSortableRow.vue",
|
|
703
817
|
"snippet": "@birdycom/omniaui/snippets/sortable-row.html"
|
|
704
818
|
},
|
|
819
|
+
{
|
|
820
|
+
"name": "OcSpecList",
|
|
821
|
+
"kind": "component",
|
|
822
|
+
"file": "src/components/OcSpecList.vue",
|
|
823
|
+
"snippet": "@birdycom/omniaui/snippets/spec-list.html"
|
|
824
|
+
},
|
|
705
825
|
{
|
|
706
826
|
"name": "OcSpinner",
|
|
707
827
|
"kind": "component",
|
|
@@ -744,6 +864,12 @@
|
|
|
744
864
|
"file": "src/components/OcStepper.vue",
|
|
745
865
|
"snippet": "@birdycom/omniaui/snippets/stepper.html"
|
|
746
866
|
},
|
|
867
|
+
{
|
|
868
|
+
"name": "OcStock",
|
|
869
|
+
"kind": "component",
|
|
870
|
+
"file": "src/components/OcStock.vue",
|
|
871
|
+
"snippet": "@birdycom/omniaui/snippets/stock.html"
|
|
872
|
+
},
|
|
747
873
|
{
|
|
748
874
|
"name": "OcSwitch",
|
|
749
875
|
"kind": "component",
|
|
@@ -936,6 +1062,12 @@
|
|
|
936
1062
|
"file": "src/blocks/OcBlockAuthSplit.vue",
|
|
937
1063
|
"snippet": "@birdycom/omniaui-blocks/blocks/auth-split.html"
|
|
938
1064
|
},
|
|
1065
|
+
{
|
|
1066
|
+
"name": "OcBlockContactForm",
|
|
1067
|
+
"kind": "block",
|
|
1068
|
+
"file": "src/blocks/OcBlockContactForm.vue",
|
|
1069
|
+
"snippet": "@birdycom/omniaui-blocks/blocks/contact-form.html"
|
|
1070
|
+
},
|
|
939
1071
|
{
|
|
940
1072
|
"name": "OcBlockContactGrid",
|
|
941
1073
|
"kind": "block",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@birdycom/omniaui-vue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "OmniaUI for Vue 3: one single-file component per OmniaUI component and block, rendering the exact markup of the CSS packages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/blocks/OcBlockContactForm.vue | Map: props -> panel (result | title, intro, alert, form: honeypot, fields, actions) -> cards -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Contact form in a panel next to a column of contact cards. Server errors come in as `errors` (field name -> text,
|
|
5
|
+
* `form` = message above the form); `sent` shows the result, `busy` sets aria-busy and disables the button.
|
|
6
|
+
* Slots: fields (own fields instead of name, e-mail, phone, subject, message), note, success, card-icon ({ card }).
|
|
7
|
+
* Emits submit (the native event; call preventDefault for Inertia) and again (back to the form).
|
|
8
|
+
* Classes: .oc-block-contact-form, -layout, -title, -intro, -cards; .oc-panel--glow, .oc-form--filled, .oc-result
|
|
9
|
+
*/
|
|
10
|
+
import { computed } from 'vue'
|
|
11
|
+
import { useUid } from '../composables/useUid'
|
|
12
|
+
|
|
13
|
+
export interface ContactFormLine {
|
|
14
|
+
text: string
|
|
15
|
+
href?: string
|
|
16
|
+
}
|
|
17
|
+
export interface ContactFormCard {
|
|
18
|
+
title: string
|
|
19
|
+
lines: ContactFormLine[]
|
|
20
|
+
/** Webicon name (mdi:email-outline …), drawn with <iconify-icon>. */
|
|
21
|
+
icon?: string
|
|
22
|
+
}
|
|
23
|
+
export interface ContactFormSubject {
|
|
24
|
+
value: string
|
|
25
|
+
label: string
|
|
26
|
+
}
|
|
27
|
+
const props = withDefaults(
|
|
28
|
+
defineProps<{
|
|
29
|
+
title?: string
|
|
30
|
+
intro?: string
|
|
31
|
+
action?: string
|
|
32
|
+
method?: string
|
|
33
|
+
subjects?: ContactFormSubject[]
|
|
34
|
+
values?: Record<string, string>
|
|
35
|
+
errors?: Record<string, string>
|
|
36
|
+
sent?: boolean
|
|
37
|
+
busy?: boolean
|
|
38
|
+
submitLabel?: string
|
|
39
|
+
busyLabel?: string
|
|
40
|
+
note?: string
|
|
41
|
+
successTitle?: string
|
|
42
|
+
successText?: string
|
|
43
|
+
againLabel?: string
|
|
44
|
+
errorText?: string
|
|
45
|
+
cards?: ContactFormCard[]
|
|
46
|
+
}>(),
|
|
47
|
+
{
|
|
48
|
+
title: 'Send us a message',
|
|
49
|
+
method: 'post',
|
|
50
|
+
subjects: () => [],
|
|
51
|
+
values: () => ({}),
|
|
52
|
+
errors: () => ({}),
|
|
53
|
+
submitLabel: 'Send message',
|
|
54
|
+
busyLabel: 'Sending…',
|
|
55
|
+
successTitle: 'Thank you, your message was sent',
|
|
56
|
+
againLabel: 'Send another message',
|
|
57
|
+
errorText: 'Please check the fields in red.',
|
|
58
|
+
cards: () => [],
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
const emit = defineEmits<{ submit: [event: Event]; again: [] }>()
|
|
62
|
+
const uid = useUid('oc-contact')
|
|
63
|
+
const fid = (name: string) => `${uid}-${name}`
|
|
64
|
+
const invalid = (name: string) => (props.errors[name] ? { 'aria-invalid': true as const, 'aria-describedby': `err-${name}` } : {})
|
|
65
|
+
const alertText = computed(() => props.errors.form ?? (Object.keys(props.errors).some((k) => k !== 'form') ? props.errorText : null))
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<template>
|
|
69
|
+
<section class="oc-block-contact-form" aria-label="Contact">
|
|
70
|
+
<div class="oc-block-contact-form-layout">
|
|
71
|
+
<div :id="uid" class="oc-panel oc-panel--glow">
|
|
72
|
+
<div v-if="sent" class="oc-result" role="status" tabindex="-1">
|
|
73
|
+
<slot name="success">
|
|
74
|
+
<span class="oc-icon-tile oc-icon-tile--sm oc-accent--green" aria-hidden="true"><iconify-icon icon="mdi:check" /></span>
|
|
75
|
+
<h2 class="oc-result__title">{{ successTitle }}</h2>
|
|
76
|
+
<p v-if="successText" class="oc-result__text">{{ successText }}</p>
|
|
77
|
+
<div class="oc-result__actions">
|
|
78
|
+
<button type="button" class="oc-btn oc-btn--outline" @click="emit('again')">{{ againLabel }}</button>
|
|
79
|
+
</div>
|
|
80
|
+
</slot>
|
|
81
|
+
</div>
|
|
82
|
+
<div v-else>
|
|
83
|
+
<h2 v-if="title" class="oc-block-contact-form-title">{{ title }}</h2>
|
|
84
|
+
<p v-if="intro" class="oc-block-contact-form-intro">{{ intro }}</p>
|
|
85
|
+
<p v-if="alertText" class="oc-alert oc-alert--danger" role="alert">{{ alertText }}</p>
|
|
86
|
+
<form class="oc-form oc-form--filled" :action="action" :method="method" novalidate :aria-busy="busy || undefined" @submit="emit('submit', $event)">
|
|
87
|
+
<div class="oc-honeypot" aria-hidden="true">
|
|
88
|
+
<label :for="fid('website')">Leave this field empty</label>
|
|
89
|
+
<input :id="fid('website')" type="text" name="website" tabindex="-1" autocomplete="off" />
|
|
90
|
+
</div>
|
|
91
|
+
<slot name="fields">
|
|
92
|
+
<div class="oc-field-group oc-field-group--row">
|
|
93
|
+
<div class="oc-field">
|
|
94
|
+
<label class="oc-field-label" :for="fid('name')">Name</label>
|
|
95
|
+
<input :id="fid('name')" class="oc-input" type="text" name="name" required maxlength="100" autocomplete="name" :value="values.name" v-bind="invalid('name')" />
|
|
96
|
+
<p v-if="errors.name" id="err-name" class="oc-field-error">{{ errors.name }}</p>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="oc-field">
|
|
99
|
+
<label class="oc-field-label" :for="fid('email')">E-mail</label>
|
|
100
|
+
<input :id="fid('email')" class="oc-input" type="email" name="email" required maxlength="254" autocomplete="email" :value="values.email" v-bind="invalid('email')" />
|
|
101
|
+
<p v-if="errors.email" id="err-email" class="oc-field-error">{{ errors.email }}</p>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
<div :class="['oc-field-group', subjects.length && 'oc-field-group--row']">
|
|
105
|
+
<div class="oc-field">
|
|
106
|
+
<label class="oc-field-label" :for="fid('phone')">Phone <span class="oc-field-label__hint">(optional)</span></label>
|
|
107
|
+
<input :id="fid('phone')" class="oc-input" type="tel" name="phone" maxlength="40" autocomplete="tel" inputmode="tel" :value="values.phone" v-bind="invalid('phone')" />
|
|
108
|
+
<p v-if="errors.phone" id="err-phone" class="oc-field-error">{{ errors.phone }}</p>
|
|
109
|
+
</div>
|
|
110
|
+
<div v-if="subjects.length" class="oc-field">
|
|
111
|
+
<label class="oc-field-label" :for="fid('subject')">What is it about?</label>
|
|
112
|
+
<select :id="fid('subject')" class="oc-select" name="subject" required v-bind="invalid('subject')">
|
|
113
|
+
<option value="" :selected="!values.subject">Choose a subject</option>
|
|
114
|
+
<option v-for="s in subjects" :key="s.value" :value="s.value" :selected="values.subject === s.value">{{ s.label }}</option>
|
|
115
|
+
</select>
|
|
116
|
+
<p v-if="errors.subject" id="err-subject" class="oc-field-error">{{ errors.subject }}</p>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="oc-field">
|
|
120
|
+
<label class="oc-field-label" :for="fid('message')">Your message</label>
|
|
121
|
+
<textarea :id="fid('message')" class="oc-input oc-textarea" name="message" rows="6" required maxlength="5000" :value="values.message" v-bind="invalid('message')" />
|
|
122
|
+
<p v-if="errors.message" id="err-message" class="oc-field-error">{{ errors.message }}</p>
|
|
123
|
+
</div>
|
|
124
|
+
</slot>
|
|
125
|
+
<div class="oc-form-actions">
|
|
126
|
+
<button class="oc-btn oc-btn--lg" type="submit" :disabled="busy">{{ busy ? busyLabel : submitLabel }}</button>
|
|
127
|
+
<p v-if="note || $slots.note" class="oc-form-note"><slot name="note">{{ note }}</slot></p>
|
|
128
|
+
</div>
|
|
129
|
+
</form>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div v-if="cards.length" class="oc-block-contact-form-cards">
|
|
133
|
+
<article v-for="(card, i) in cards" :key="i" class="oc-block-contact-card">
|
|
134
|
+
<span v-if="card.icon || $slots['card-icon']" class="oc-icon-tile oc-icon-tile--sm" aria-hidden="true">
|
|
135
|
+
<slot name="card-icon" :card="card"><iconify-icon :icon="card.icon" /></slot>
|
|
136
|
+
</span>
|
|
137
|
+
<h3 class="oc-block-contact-title">{{ card.title }}</h3>
|
|
138
|
+
<template v-for="(line, j) in card.lines" :key="j">
|
|
139
|
+
<a v-if="line.href" class="oc-block-contact-line" :href="line.href">{{ line.text }}</a>
|
|
140
|
+
<p v-else class="oc-block-contact-line">{{ line.text }}</p>
|
|
141
|
+
</template>
|
|
142
|
+
</article>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</section>
|
|
146
|
+
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* Card with title, actions, toolbar, table, bulk bar and footer. Put an <OcTable> in the default slot.
|
|
4
|
-
* Slots: actions (header buttons), toolbar (.oc-table-toolbar content: search, filter pills
|
|
4
|
+
* Slots: actions (header buttons), toolbar (.oc-table-toolbar content: search, filter pills),
|
|
5
5
|
* bulk (selection bar, shown when `selectedCount` > 0), default (table), footer (.oc-table-footer: entries text, page size, pagination).
|
|
6
6
|
* Classes: .oc-card.oc-block-datatable, -head, -title, -description, -body, -bulk, .oc-table-toolbar, .oc-table-footer
|
|
7
7
|
*/
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
<script setup lang="ts">
|
|
3
3
|
/**
|
|
4
4
|
* Profile / channel header: banner, overhanging avatar, name, meta, bio, actions and link tabs.
|
|
5
|
-
* Slots: avatar (initials or <img>), actions, bio
|
|
5
|
+
* Slots: avatar (initials or <img>), actions, bio, panel-0, panel-1 … (with panel slots the tabs switch on the page
|
|
6
|
+
* itself: radios + panels, CSS only; without them the tabs are links). Classes: .oc-block-profile (--compact), -banner, -bar, -avatar,
|
|
6
7
|
* -info, -name, -meta, -bio, -actions, -tabs, -tab (aria-current="page")
|
|
7
8
|
*/
|
|
8
|
-
import { computed } from 'vue'
|
|
9
|
+
import { computed, useSlots } from 'vue'
|
|
9
10
|
import { useUid } from '../composables/useUid'
|
|
10
11
|
|
|
11
12
|
export interface ProfileHeaderTab {
|
|
12
13
|
label: string
|
|
13
|
-
href
|
|
14
|
+
href?: string
|
|
14
15
|
current?: boolean
|
|
15
16
|
}
|
|
16
17
|
const props = withDefaults(
|
|
@@ -28,6 +29,8 @@ const props = withDefaults(
|
|
|
28
29
|
{ tabs: () => [], tabsLabel: 'Profile sections', headingLevel: 1 },
|
|
29
30
|
)
|
|
30
31
|
const nameId = useUid('oc-profile-name')
|
|
32
|
+
const slots = useSlots()
|
|
33
|
+
const panels = computed(() => props.tabs.some((_, i) => Boolean(slots[`panel-${i}`])))
|
|
31
34
|
const bannerStyle = computed(() => (props.banner ? `--oc-profile-banner: url("${props.banner}")` : undefined))
|
|
32
35
|
</script>
|
|
33
36
|
|
|
@@ -43,7 +46,15 @@ const bannerStyle = computed(() => (props.banner ? `--oc-profile-banner: url("${
|
|
|
43
46
|
</div>
|
|
44
47
|
<div v-if="$slots.actions" class="oc-block-profile-actions"><slot name="actions" /></div>
|
|
45
48
|
</div>
|
|
46
|
-
<
|
|
49
|
+
<template v-if="tabs.length && panels">
|
|
50
|
+
<div class="oc-block-profile-tabs" role="radiogroup" :aria-label="tabsLabel">
|
|
51
|
+
<label v-for="(tab, i) in tabs" :key="i" class="oc-block-profile-tab"><input type="radio" :name="`${nameId}-tab`" :checked="tab.current ?? i === 0">{{ tab.label }}</label>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="oc-block-profile-panels">
|
|
54
|
+
<div v-for="(tab, i) in tabs" :key="i" class="oc-block-profile-panel"><slot :name="`panel-${i}`" /></div>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
<nav v-else-if="tabs.length" class="oc-block-profile-tabs" :aria-label="tabsLabel">
|
|
47
58
|
<a v-for="(tab, i) in tabs" :key="i" class="oc-block-profile-tab" :href="tab.href" :aria-current="tab.current ? 'page' : undefined">{{ tab.label }}</a>
|
|
48
59
|
</nav>
|
|
49
60
|
</section>
|
|
@@ -10,7 +10,7 @@ withDefaults(defineProps<{ items?: AttachmentFile[]; stack?: boolean; as?: 'ul'
|
|
|
10
10
|
as: 'ul',
|
|
11
11
|
label: 'Files in this message',
|
|
12
12
|
items: () => [
|
|
13
|
-
{ name: 'Quote kitchen
|
|
13
|
+
{ name: 'Quote kitchen Project 1 v2.pdf', size: '1.2 MB', href: '#quote.pdf' },
|
|
14
14
|
{ name: 'Measurements ground floor.xlsx', size: '86 KB', href: '#measurements.xlsx' },
|
|
15
15
|
{ name: 'Contract addendum.docx', size: '240 KB', href: '#contract.docx' },
|
|
16
16
|
{ name: 'Plans architect.zip', size: '14.8 MB', href: '#plans.zip' },
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcAvatarPicker.vue | Map: props -> preview + file input -> text and buttons -> change event on mount -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/** Avatar or logo picker with a preview, Upload and Remove, and drag and drop. Emits change (File or null) and invalid (File, reason) for a wrong type or a file over maxKb. */
|
|
4
|
+
import { onMounted, useId, useTemplateRef } from 'vue'
|
|
5
|
+
import { bindAvatarPicker } from '../../../ui/src/form-extras.js'
|
|
6
|
+
|
|
7
|
+
withDefaults(defineProps<{ initials: string; label?: string; hint?: string; logo?: boolean; name?: string; accept?: string; maxKb?: number }>(), { label: 'Profile picture', logo: false, accept: 'image/*' })
|
|
8
|
+
const emit = defineEmits<{ change: [file: File | null]; invalid: [file: File, reason: string] }>()
|
|
9
|
+
const id = useId()
|
|
10
|
+
const root = useTemplateRef<HTMLDivElement>('root')
|
|
11
|
+
onMounted(() => {
|
|
12
|
+
if (!root.value) return
|
|
13
|
+
bindAvatarPicker(root.value)
|
|
14
|
+
root.value.addEventListener('oc-avatar-change', (e) => emit('change', (e as CustomEvent<{ file: File | null }>).detail.file))
|
|
15
|
+
root.value.addEventListener('oc-avatar-invalid', (e) => { const d = (e as CustomEvent<{ file: File; reason: string }>).detail; emit('invalid', d.file, d.reason) })
|
|
16
|
+
})
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div ref="root" :class="['oc-avatar-picker', logo && 'oc-avatar-picker--square oc-avatar-picker--logo']" :data-oc-max-kb="maxKb">
|
|
21
|
+
<label class="oc-avatar-picker__preview" :for="id" title="Choose an image">
|
|
22
|
+
<span data-oc-initials>{{ initials }}</span>
|
|
23
|
+
<input :id="id" class="oc-avatar-picker__file" type="file" :accept="accept" :name="name">
|
|
24
|
+
</label>
|
|
25
|
+
<div class="oc-avatar-picker__text">
|
|
26
|
+
<span class="oc-avatar-picker__label">{{ label }}</span>
|
|
27
|
+
<span v-if="hint" class="oc-avatar-picker__hint">{{ hint }}</span>
|
|
28
|
+
<div class="oc-avatar-picker__actions">
|
|
29
|
+
<button type="button" class="oc-btn oc-btn--outline oc-btn--sm" data-oc-avatar-upload><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9 16v-6H5l7-7l7 7h-4v6zm-4 4v-2h14v2z" /></svg>Upload</button>
|
|
30
|
+
<button type="button" class="oc-btn oc-btn--ghost oc-btn--sm" data-oc-avatar-remove><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z" /></svg>Remove</button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcBarcode.vue | Map: props -> encode -> svg + digits -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/** EAN-13, EAN-8, UPC-A or Code 128 as SVG; shows the reason when the value cannot be encoded. */
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
import { barsToSvg, encodeBarcode } from '../../../ui/src/barcode.js'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<{ value: string; format?: 'auto' | 'EAN13' | 'EAN8' | 'UPC' | 'CODE128'; digits?: boolean; size?: 'md' | 'hero' | 'compact' }>(), { format: 'auto', digits: true, size: 'md' })
|
|
8
|
+
const result = computed(() => {
|
|
9
|
+
try {
|
|
10
|
+
const { bits, text } = encodeBarcode(props.value, props.format)
|
|
11
|
+
return { svg: barsToSvg(bits, `Barcode ${text}`), text, error: '' }
|
|
12
|
+
} catch (err) {
|
|
13
|
+
return { svg: '', text: props.value, error: (err as Error).message }
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<figure :class="['oc-barcode', size !== 'md' && `oc-barcode--${size}`]" :data-error="result.error ? '' : undefined">
|
|
20
|
+
<span v-if="result.svg" style="display: contents" v-html="result.svg"></span>
|
|
21
|
+
<figcaption v-if="digits || result.error" class="oc-barcode__digits">{{ result.error ? `${value}: ${result.error}` : result.text }}</figcaption>
|
|
22
|
+
</figure>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- File: packages/vue/src/components/OcBrandMark.vue | Map: link -> icon (slot) -> word (name + accent) -> tagline -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/** Logo from an icon and text (snippets/brand-mark.html). Slot icon: an <svg class="oc-brand-mark__icon"> or <img>. */
|
|
4
|
+
withDefaults(defineProps<{ name: string; accent?: string; tagline?: string; href?: string; size?: 'sm' | 'md' | 'lg' }>(), { href: '/', size: 'md' })
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<a :class="['oc-brand-mark', size !== 'md' && `oc-brand-mark--${size}`]" :href="href">
|
|
9
|
+
<slot name="icon" />
|
|
10
|
+
<span class="oc-brand-mark__text">
|
|
11
|
+
<span class="oc-brand-mark__word">{{ name }}<span v-if="accent" class="oc-brand-mark__accent">{{ accent }}</span></span>
|
|
12
|
+
<span v-if="tagline" class="oc-brand-mark__tagline">{{ tagline }}</span>
|
|
13
|
+
</span>
|
|
14
|
+
</a>
|
|
15
|
+
</template>
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Breadcrumb. Pass `items` (the last one is the current page) or use the default slot.
|
|
4
|
+
* `icon` per item is a webicon name (mdi:home-outline …), drawn with <iconify-icon>; separator="chevron" puts › between.
|
|
5
|
+
*/
|
|
3
6
|
export interface BreadcrumbItem {
|
|
4
7
|
label: string
|
|
5
8
|
href?: string
|
|
9
|
+
icon?: string
|
|
6
10
|
}
|
|
7
|
-
withDefaults(defineProps<{ items?: BreadcrumbItem[]; label?: string }>(), {
|
|
11
|
+
withDefaults(defineProps<{ items?: BreadcrumbItem[]; label?: string; separator?: 'slash' | 'chevron' }>(), {
|
|
8
12
|
items: () => [],
|
|
9
13
|
label: 'Breadcrumb',
|
|
14
|
+
separator: 'slash',
|
|
10
15
|
})
|
|
11
16
|
</script>
|
|
12
17
|
|
|
13
18
|
<template>
|
|
14
|
-
<nav class="oc-breadcrumb" :aria-label="label">
|
|
19
|
+
<nav :class="['oc-breadcrumb', separator === 'chevron' && 'oc-breadcrumb--chevron']" :aria-label="label">
|
|
15
20
|
<slot>
|
|
16
21
|
<template v-for="(item, i) in items" :key="i">
|
|
17
|
-
<a v-if="item.href && i < items.length - 1" class="oc-breadcrumb-item" :href="item.href"
|
|
18
|
-
|
|
22
|
+
<a v-if="item.href && i < items.length - 1" class="oc-breadcrumb-item" :href="item.href"
|
|
23
|
+
><iconify-icon v-if="item.icon" :icon="item.icon" aria-hidden="true" />{{ item.label }}</a
|
|
24
|
+
>
|
|
25
|
+
<span v-else class="oc-breadcrumb-item" aria-current="page"
|
|
26
|
+
><iconify-icon v-if="item.icon" :icon="item.icon" aria-hidden="true" />{{ item.label }}</span
|
|
27
|
+
>
|
|
19
28
|
</template>
|
|
20
29
|
</slot>
|
|
21
30
|
</nav>
|
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Button. Renders <a> when `href` is set, otherwise <button>.
|
|
4
4
|
* Classes: .oc-btn + --secondary/--outline/--ghost/--link/--destructive/--success/--gradient/--glass,
|
|
5
|
-
* --
|
|
5
|
+
* --soft-primary/-success/-warning/-destructive/-info (row actions), --plain (icon only, no surface),
|
|
6
|
+
* --xs/--sm/--lg, --icon, --pill, --inline (link button inside running text). `loading` sets aria-busy (spinner).
|
|
6
7
|
*/
|
|
7
8
|
withDefaults(
|
|
8
9
|
defineProps<{
|
|
9
|
-
variant?:
|
|
10
|
+
variant?:
|
|
11
|
+
| 'default' | 'secondary' | 'outline' | 'ghost' | 'link' | 'destructive' | 'success' | 'gradient' | 'glass'
|
|
12
|
+
| 'soft-primary' | 'soft-success' | 'soft-warning' | 'soft-destructive' | 'soft-info' | 'plain'
|
|
10
13
|
size?: 'xs' | 'sm' | 'md' | 'lg'
|
|
11
14
|
icon?: boolean
|
|
12
15
|
pill?: boolean
|
|
13
16
|
inline?: boolean
|
|
17
|
+
loading?: boolean
|
|
18
|
+
disabled?: boolean
|
|
14
19
|
href?: string
|
|
15
20
|
type?: 'button' | 'submit' | 'reset'
|
|
16
21
|
}>(),
|
|
@@ -22,6 +27,7 @@ withDefaults(
|
|
|
22
27
|
<a
|
|
23
28
|
v-if="href"
|
|
24
29
|
:href="href"
|
|
30
|
+
:aria-busy="loading || undefined"
|
|
25
31
|
:class="[
|
|
26
32
|
'oc-btn',
|
|
27
33
|
variant !== 'default' && `oc-btn--${variant}`,
|
|
@@ -36,6 +42,8 @@ withDefaults(
|
|
|
36
42
|
<button
|
|
37
43
|
v-else
|
|
38
44
|
:type="type"
|
|
45
|
+
:aria-busy="loading || undefined"
|
|
46
|
+
:disabled="disabled || loading"
|
|
39
47
|
:class="[
|
|
40
48
|
'oc-btn',
|
|
41
49
|
variant !== 'default' && `oc-btn--${variant}`,
|