@birdycom/omniaui-vue 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +49 -49
- package/dist/omniaui-vue.js.map +1 -1
- package/package.json +69 -69
- package/src/blocks/OcBlockAboutTimeline.vue +44 -44
- package/src/blocks/OcBlockAppHeader.vue +36 -36
- package/src/blocks/OcBlockAppShell.vue +29 -29
- package/src/blocks/OcBlockAuth.vue +24 -24
- package/src/blocks/OcBlockAuthSplit.vue +47 -47
- package/src/blocks/OcBlockContactGrid.vue +53 -53
- package/src/blocks/OcBlockCta.vue +12 -12
- package/src/blocks/OcBlockDataTable.vue +27 -27
- package/src/blocks/OcBlockFaq.vue +49 -49
- package/src/blocks/OcBlockFeatures.vue +23 -23
- package/src/blocks/OcBlockFooter.vue +25 -25
- package/src/blocks/OcBlockFormPage.vue +24 -24
- package/src/blocks/OcBlockHero.vue +13 -13
- package/src/blocks/OcBlockHeroCode.vue +42 -42
- package/src/blocks/OcBlockHeroOrbit.vue +69 -69
- package/src/blocks/OcBlockHeroSplit.vue +15 -15
- package/src/blocks/OcBlockModuleHeader.vue +21 -21
- package/src/blocks/OcBlockPageHero.vue +13 -13
- package/src/blocks/OcBlockPortfolioGrid.vue +63 -63
- package/src/blocks/OcBlockPricing.vue +39 -39
- package/src/blocks/OcBlockProcess.vue +39 -39
- package/src/blocks/OcBlockProductGrid.vue +41 -41
- package/src/blocks/OcBlockProfileHeader.vue +50 -50
- package/src/blocks/OcBlockShowcase.vue +43 -43
- package/src/blocks/OcBlockSiteFooter.vue +54 -54
- package/src/blocks/OcBlockSiteHeader.vue +34 -34
- package/src/blocks/OcBlockSpotlight.vue +47 -47
- package/src/blocks/OcBlockStats.vue +17 -17
- package/src/blocks/OcBlockStatsCounter.vue +20 -20
- package/src/blocks/OcBlockTeamList.vue +53 -53
- package/src/blocks/OcBlockTwoFactor.vue +61 -61
- package/src/blocks/OcBlockWizardDialog.vue +67 -67
- package/src/components/OcAccordion.vue +12 -12
- package/src/components/OcAccordionAccent.vue +27 -27
- package/src/components/OcAccordionItem.vue +19 -19
- package/src/components/OcAffixInput.vue +40 -40
- package/src/components/OcAlert.vue +17 -17
- package/src/components/OcAlertDialog.vue +66 -66
- package/src/components/OcAlphaTabs.vue +54 -54
- package/src/components/OcAspectRatio.vue +21 -21
- package/src/components/OcAttachment.vue +59 -59
- package/src/components/OcAttachmentImage.vue +13 -13
- package/src/components/OcAttachmentList.vue +31 -31
- package/src/components/OcAttachmentThumb.vue +14 -14
- package/src/components/OcAvatar.vue +18 -18
- package/src/components/OcAvatarGroup.vue +7 -7
- package/src/components/OcBackLink.vue +8 -8
- package/src/components/OcBadge.vue +13 -13
- package/src/components/OcBarcodeInput.vue +51 -51
- package/src/components/OcBreadcrumb.vue +22 -22
- package/src/components/OcBrowserFrame.vue +33 -33
- package/src/components/OcBusyOverlay.vue +32 -32
- package/src/components/OcButton.vue +47 -47
- package/src/components/OcButtonGroup.vue +10 -10
- package/src/components/OcButtonGroupText.vue +7 -7
- package/src/components/OcCard.vue +20 -20
- package/src/components/OcChartLine.vue +80 -80
- package/src/components/OcChatComposer.vue +42 -42
- package/src/components/OcCheckbox.vue +26 -26
- package/src/components/OcChecklist.vue +29 -29
- package/src/components/OcCollapsible.vue +19 -19
- package/src/components/OcColorField.vue +15 -15
- package/src/components/OcColorSelect.vue +33 -33
- package/src/components/OcColumnMenu.vue +50 -50
- package/src/components/OcCommand.vue +119 -119
- package/src/components/OcConfirmDialog.vue +65 -65
- package/src/components/OcCookieConsent.vue +119 -119
- package/src/components/OcCopyField.vue +37 -37
- package/src/components/OcCounter.vue +15 -15
- package/src/components/OcDataTable.vue +144 -144
- package/src/components/OcDefinitionList.vue +35 -35
- package/src/components/OcDropdown.vue +23 -23
- package/src/components/OcDropdownItem.vue +9 -9
- package/src/components/OcDropdownLabel.vue +7 -7
- package/src/components/OcDropdownSeparator.vue +7 -7
- package/src/components/OcDropzone.vue +36 -36
- package/src/components/OcEmojiPicker.vue +45 -45
- package/src/components/OcEmptyState.vue +13 -13
- package/src/components/OcEntityCard.vue +47 -47
- package/src/components/OcFactList.vue +37 -37
- package/src/components/OcFilterCounts.vue +38 -38
- package/src/components/OcFlipCard.vue +36 -36
- package/src/components/OcFormField.vue +36 -36
- package/src/components/OcGreeting.vue +52 -52
- package/src/components/OcHeading.vue +19 -19
- package/src/components/OcHoverCard.vue +25 -25
- package/src/components/OcImageZoom.vue +39 -39
- package/src/components/OcInput.vue +9 -9
- package/src/components/OcInputGroup.vue +12 -12
- package/src/components/OcItem.vue +40 -40
- package/src/components/OcItemGroup.vue +7 -7
- package/src/components/OcItemSeparator.vue +7 -7
- package/src/components/OcKbd.vue +7 -7
- package/src/components/OcKbdGroup.vue +15 -15
- package/src/components/OcLabel.vue +10 -10
- package/src/components/OcLanguageSelect.vue +40 -40
- package/src/components/OcLauncherCard.vue +34 -34
- package/src/components/OcLightbox.vue +63 -63
- package/src/components/OcLinkArrow.vue +25 -25
- package/src/components/OcListToolbar.vue +44 -44
- package/src/components/OcMarquee.vue +36 -36
- package/src/components/OcMediaCard.vue +28 -28
- package/src/components/OcMediaRow.vue +28 -28
- package/src/components/OcMediaThumb.vue +38 -38
- package/src/components/OcMessage.vue +91 -91
- package/src/components/OcMessageList.vue +43 -43
- package/src/components/OcMessageThread.vue +155 -155
- package/src/components/OcModal.vue +42 -42
- package/src/components/OcNavmenu.vue +18 -18
- package/src/components/OcNavmenuList.vue +38 -38
- package/src/components/OcNextTeaser.vue +25 -25
- package/src/components/OcNumberInput.vue +47 -47
- package/src/components/OcOtpInput.vue +34 -34
- package/src/components/OcPageHeader.vue +34 -34
- package/src/components/OcPagination.vue +33 -33
- package/src/components/OcPasswordInput.vue +27 -27
- package/src/components/OcPasswordRequirements.vue +28 -28
- package/src/components/OcPhoneInput.vue +72 -72
- package/src/components/OcPopover.vue +25 -25
- package/src/components/OcPriceCard.vue +69 -69
- package/src/components/OcProductMark.vue +31 -31
- package/src/components/OcProgress.vue +21 -21
- package/src/components/OcProjectCard.vue +36 -36
- package/src/components/OcProse.vue +57 -57
- package/src/components/OcRadio.vue +40 -40
- package/src/components/OcRadioGroup.vue +32 -32
- package/src/components/OcRecoveryCodes.vue +55 -55
- package/src/components/OcScrollArea.vue +15 -15
- package/src/components/OcSegmented.vue +28 -28
- package/src/components/OcSelect.vue +21 -21
- package/src/components/OcSeparator.vue +18 -18
- package/src/components/OcSeverityCard.vue +46 -46
- package/src/components/OcSheet.vue +49 -49
- package/src/components/OcSidebarCollapsible.vue +19 -19
- package/src/components/OcSidebarGroup.vue +11 -11
- package/src/components/OcSidebarMenu.vue +7 -7
- package/src/components/OcSidebarMenuButton.vue +31 -31
- package/src/components/OcSidebarSeparator.vue +7 -7
- package/src/components/OcSkeleton.vue +8 -8
- package/src/components/OcSlider.vue +27 -27
- package/src/components/OcSortableRow.vue +40 -40
- package/src/components/OcSpinner.vue +13 -13
- package/src/components/OcStackCard.vue +24 -24
- package/src/components/OcStackList.vue +31 -31
- package/src/components/OcStatCard.vue +39 -39
- package/src/components/OcStepper.vue +34 -34
- package/src/components/OcSwitch.vue +15 -15
- package/src/components/OcTable.vue +115 -115
- package/src/components/OcTableActions.vue +26 -26
- package/src/components/OcTableCompact.vue +46 -46
- package/src/components/OcTabs.vue +37 -37
- package/src/components/OcTagInput.vue +50 -50
- package/src/components/OcTerminalLog.vue +50 -50
- package/src/components/OcTextarea.vue +9 -9
- package/src/components/OcThemeSwitch.vue +31 -31
- package/src/components/OcTimeInput.vue +50 -50
- package/src/components/OcTimeline.vue +36 -36
- package/src/components/OcToast.vue +31 -31
- package/src/components/OcToastContainer.vue +21 -21
- package/src/components/OcToggle.vue +63 -63
- package/src/components/OcToggleField.vue +28 -28
- package/src/components/OcToggleGroup.vue +40 -40
- package/src/components/OcTooltip.vue +8 -8
- package/src/components/OcTopProgress.vue +54 -54
- package/src/components/OcTree.vue +70 -70
- package/src/components/OcUpgradeBanner.vue +42 -42
- package/src/components/OcUser.vue +25 -25
- package/src/components/OcVersionBanner.vue +34 -34
- package/src/composables/chartLine.ts +28 -28
- package/src/composables/keys.ts +22 -22
- package/src/composables/useAttrsSplit.ts +16 -16
- package/src/composables/useDialog.ts +21 -21
- package/src/composables/useToast.ts +66 -66
- package/src/composables/useUid.ts +12 -12
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcImageZoom.vue | Map: props + v-model:zoom -> toolbar (radio levels) -> viewport -> canvas -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Zoom / pan viewport (same markup as snippets/image-zoom.html). v-model:zoom = current level (default 1).
|
|
5
|
-
* levels: toolbar values (default [1, 1.5, 2, 3]; [] = no toolbar). ratio: square | photo | CSS ratio.
|
|
6
|
-
* Default slot replaces the <img>. Classes: .oc-image-zoom(--square/--photo), __toolbar, __label, __level, __viewport, __canvas
|
|
7
|
-
*/
|
|
8
|
-
import { computed } from 'vue'
|
|
9
|
-
import { useUid } from '../composables/useUid'
|
|
10
|
-
|
|
11
|
-
const props = withDefaults(defineProps<{ src?: string; alt?: string; levels?: number[]; ratio?: string }>(), {
|
|
12
|
-
alt: '',
|
|
13
|
-
levels: () => [1, 1.5, 2, 3],
|
|
14
|
-
})
|
|
15
|
-
const zoom = defineModel<number>('zoom', { default: 1 })
|
|
16
|
-
const name = useUid('oc-zoom')
|
|
17
|
-
const preset = computed(() => (props.ratio === 'square' || props.ratio === 'photo' ? props.ratio : undefined))
|
|
18
|
-
// Writing --oc-zoom inline means any level works, not only the ones the CSS :has() rules know.
|
|
19
|
-
const vars = computed(() => ({ '--oc-zoom': String(zoom.value), ...(props.ratio && !preset.value ? { '--oc-image-zoom-ratio': props.ratio } : {}) }))
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<template>
|
|
23
|
-
<div :class="['oc-image-zoom', preset && `oc-image-zoom--${preset}`]" :style="vars">
|
|
24
|
-
<div v-if="levels.length" class="oc-image-zoom__toolbar" role="radiogroup" aria-label="Zoom level">
|
|
25
|
-
<span class="oc-image-zoom__label">Zoom</span>
|
|
26
|
-
<label v-for="l in levels" :key="l" class="oc-image-zoom__level">
|
|
27
|
-
<input v-model="zoom" type="radio" :name="name" :value="l" />{{ Math.round(l * 100) }}%
|
|
28
|
-
</label>
|
|
29
|
-
</div>
|
|
30
|
-
<div class="oc-image-zoom__viewport" tabindex="0" role="region" :aria-label="alt ? `${alt}, scroll to pan` : 'Scroll to pan'">
|
|
31
|
-
<div class="oc-image-zoom__canvas">
|
|
32
|
-
<slot>
|
|
33
|
-
<img v-if="src" :src="src" :alt="alt" />
|
|
34
|
-
<div v-else class="oc-media-art" role="img" :aria-label="alt" />
|
|
35
|
-
</slot>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcImageZoom.vue | Map: props + v-model:zoom -> toolbar (radio levels) -> viewport -> canvas -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Zoom / pan viewport (same markup as snippets/image-zoom.html). v-model:zoom = current level (default 1).
|
|
5
|
+
* levels: toolbar values (default [1, 1.5, 2, 3]; [] = no toolbar). ratio: square | photo | CSS ratio.
|
|
6
|
+
* Default slot replaces the <img>. Classes: .oc-image-zoom(--square/--photo), __toolbar, __label, __level, __viewport, __canvas
|
|
7
|
+
*/
|
|
8
|
+
import { computed } from 'vue'
|
|
9
|
+
import { useUid } from '../composables/useUid'
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<{ src?: string; alt?: string; levels?: number[]; ratio?: string }>(), {
|
|
12
|
+
alt: '',
|
|
13
|
+
levels: () => [1, 1.5, 2, 3],
|
|
14
|
+
})
|
|
15
|
+
const zoom = defineModel<number>('zoom', { default: 1 })
|
|
16
|
+
const name = useUid('oc-zoom')
|
|
17
|
+
const preset = computed(() => (props.ratio === 'square' || props.ratio === 'photo' ? props.ratio : undefined))
|
|
18
|
+
// Writing --oc-zoom inline means any level works, not only the ones the CSS :has() rules know.
|
|
19
|
+
const vars = computed(() => ({ '--oc-zoom': String(zoom.value), ...(props.ratio && !preset.value ? { '--oc-image-zoom-ratio': props.ratio } : {}) }))
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<div :class="['oc-image-zoom', preset && `oc-image-zoom--${preset}`]" :style="vars">
|
|
24
|
+
<div v-if="levels.length" class="oc-image-zoom__toolbar" role="radiogroup" aria-label="Zoom level">
|
|
25
|
+
<span class="oc-image-zoom__label">Zoom</span>
|
|
26
|
+
<label v-for="l in levels" :key="l" class="oc-image-zoom__level">
|
|
27
|
+
<input v-model="zoom" type="radio" :name="name" :value="l" />{{ Math.round(l * 100) }}%
|
|
28
|
+
</label>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="oc-image-zoom__viewport" tabindex="0" role="region" :aria-label="alt ? `${alt}, scroll to pan` : 'Scroll to pan'">
|
|
31
|
+
<div class="oc-image-zoom__canvas">
|
|
32
|
+
<slot>
|
|
33
|
+
<img v-if="src" :src="src" :alt="alt" />
|
|
34
|
+
<div v-else class="oc-media-art" role="img" :aria-label="alt" />
|
|
35
|
+
</slot>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Text input. v-model. `error` sets .oc-input--error + aria-invalid. Any native attribute falls through. */
|
|
3
|
-
defineProps<{ error?: boolean }>()
|
|
4
|
-
const model = defineModel<string | number>()
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<template>
|
|
8
|
-
<input v-model="model" :class="['oc-input', error && 'oc-input--error']" :aria-invalid="error ? 'true' : undefined" />
|
|
9
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Text input. v-model. `error` sets .oc-input--error + aria-invalid. Any native attribute falls through. */
|
|
3
|
+
defineProps<{ error?: boolean }>()
|
|
4
|
+
const model = defineModel<string | number>()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<input v-model="model" :class="['oc-input', error && 'oc-input--error']" :aria-invalid="error ? 'true' : undefined" />
|
|
9
|
+
</template>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Input with prefix/suffix addon. Default slot = the input(s); `prefix` / `suffix` are text props or slots. */
|
|
3
|
-
defineProps<{ prefix?: string; suffix?: string }>()
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<template>
|
|
7
|
-
<div class="oc-input-group">
|
|
8
|
-
<span v-if="prefix || $slots.prefix" class="oc-input-addon"><slot name="prefix">{{ prefix }}</slot></span>
|
|
9
|
-
<slot />
|
|
10
|
-
<span v-if="suffix || $slots.suffix" class="oc-input-addon"><slot name="suffix">{{ suffix }}</slot></span>
|
|
11
|
-
</div>
|
|
12
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Input with prefix/suffix addon. Default slot = the input(s); `prefix` / `suffix` are text props or slots. */
|
|
3
|
+
defineProps<{ prefix?: string; suffix?: string }>()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="oc-input-group">
|
|
8
|
+
<span v-if="prefix || $slots.prefix" class="oc-input-addon"><slot name="prefix">{{ prefix }}</slot></span>
|
|
9
|
+
<slot />
|
|
10
|
+
<span v-if="suffix || $slots.suffix" class="oc-input-addon"><slot name="suffix">{{ suffix }}</slot></span>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* List row. Renders <a> when `href` is set. Slots: media (left; `icon` boxes it, `image` makes it an image),
|
|
4
|
-
* default (replaces title + description), actions (right), header / footer (full-width rows).
|
|
5
|
-
* Classes: .oc-item(--outline/--muted/--sm/--interactive), -media(--icon/--image), -content, -title, -description,
|
|
6
|
-
* -actions, -header, -footer
|
|
7
|
-
*/
|
|
8
|
-
withDefaults(
|
|
9
|
-
defineProps<{
|
|
10
|
-
variant?: 'default' | 'outline' | 'muted'
|
|
11
|
-
size?: 'sm' | 'md'
|
|
12
|
-
interactive?: boolean
|
|
13
|
-
href?: string
|
|
14
|
-
title?: string
|
|
15
|
-
description?: string
|
|
16
|
-
icon?: boolean
|
|
17
|
-
image?: boolean
|
|
18
|
-
}>(),
|
|
19
|
-
{ variant: 'default', size: 'md' },
|
|
20
|
-
)
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<template>
|
|
24
|
-
<component
|
|
25
|
-
:is="href ? 'a' : 'div'"
|
|
26
|
-
:href="href"
|
|
27
|
-
:class="['oc-item', variant !== 'default' && `oc-item--${variant}`, size === 'sm' && 'oc-item--sm', interactive && 'oc-item--interactive']"
|
|
28
|
-
>
|
|
29
|
-
<div v-if="$slots.header" class="oc-item-header"><slot name="header" /></div>
|
|
30
|
-
<div v-if="$slots.media" :class="['oc-item-media', icon && 'oc-item-media--icon', image && 'oc-item-media--image']"><slot name="media" /></div>
|
|
31
|
-
<div class="oc-item-content">
|
|
32
|
-
<slot>
|
|
33
|
-
<p v-if="title" class="oc-item-title">{{ title }}</p>
|
|
34
|
-
<p v-if="description" class="oc-item-description">{{ description }}</p>
|
|
35
|
-
</slot>
|
|
36
|
-
</div>
|
|
37
|
-
<div v-if="$slots.actions" class="oc-item-actions"><slot name="actions" /></div>
|
|
38
|
-
<div v-if="$slots.footer" class="oc-item-footer"><slot name="footer" /></div>
|
|
39
|
-
</component>
|
|
40
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* List row. Renders <a> when `href` is set. Slots: media (left; `icon` boxes it, `image` makes it an image),
|
|
4
|
+
* default (replaces title + description), actions (right), header / footer (full-width rows).
|
|
5
|
+
* Classes: .oc-item(--outline/--muted/--sm/--interactive), -media(--icon/--image), -content, -title, -description,
|
|
6
|
+
* -actions, -header, -footer
|
|
7
|
+
*/
|
|
8
|
+
withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
variant?: 'default' | 'outline' | 'muted'
|
|
11
|
+
size?: 'sm' | 'md'
|
|
12
|
+
interactive?: boolean
|
|
13
|
+
href?: string
|
|
14
|
+
title?: string
|
|
15
|
+
description?: string
|
|
16
|
+
icon?: boolean
|
|
17
|
+
image?: boolean
|
|
18
|
+
}>(),
|
|
19
|
+
{ variant: 'default', size: 'md' },
|
|
20
|
+
)
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<component
|
|
25
|
+
:is="href ? 'a' : 'div'"
|
|
26
|
+
:href="href"
|
|
27
|
+
:class="['oc-item', variant !== 'default' && `oc-item--${variant}`, size === 'sm' && 'oc-item--sm', interactive && 'oc-item--interactive']"
|
|
28
|
+
>
|
|
29
|
+
<div v-if="$slots.header" class="oc-item-header"><slot name="header" /></div>
|
|
30
|
+
<div v-if="$slots.media" :class="['oc-item-media', icon && 'oc-item-media--icon', image && 'oc-item-media--image']"><slot name="media" /></div>
|
|
31
|
+
<div class="oc-item-content">
|
|
32
|
+
<slot>
|
|
33
|
+
<p v-if="title" class="oc-item-title">{{ title }}</p>
|
|
34
|
+
<p v-if="description" class="oc-item-description">{{ description }}</p>
|
|
35
|
+
</slot>
|
|
36
|
+
</div>
|
|
37
|
+
<div v-if="$slots.actions" class="oc-item-actions"><slot name="actions" /></div>
|
|
38
|
+
<div v-if="$slots.footer" class="oc-item-footer"><slot name="footer" /></div>
|
|
39
|
+
</component>
|
|
40
|
+
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Vertical list of OcItem. `separated` puts a separator between the direct children is left to OcItemSeparator. Class: .oc-item-group */
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<template>
|
|
6
|
-
<div class="oc-item-group"><slot /></div>
|
|
7
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Vertical list of OcItem. `separated` puts a separator between the direct children is left to OcItemSeparator. Class: .oc-item-group */
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div class="oc-item-group"><slot /></div>
|
|
7
|
+
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Divider between items in an OcItemGroup. Class: .oc-item-separator */
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<template>
|
|
6
|
-
<hr class="oc-item-separator" />
|
|
7
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Divider between items in an OcItemGroup. Class: .oc-item-separator */
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<hr class="oc-item-separator" />
|
|
7
|
+
</template>
|
package/src/components/OcKbd.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** One key. Class: .oc-kbd */
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<template>
|
|
6
|
-
<kbd class="oc-kbd"><slot /></kbd>
|
|
7
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** One key. Class: .oc-kbd */
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<kbd class="oc-kbd"><slot /></kbd>
|
|
7
|
+
</template>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Shortcut: `keys` ["Ctrl", "K"] renders one key per entry, with a "+" separator when `separator` is set. Or pass OcKbd in the slot. Classes: .oc-kbd-group, .oc-kbd-sep */
|
|
3
|
-
defineProps<{ keys?: string[]; separator?: boolean }>()
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<template>
|
|
7
|
-
<span class="oc-kbd-group">
|
|
8
|
-
<slot>
|
|
9
|
-
<template v-for="(key, i) in keys" :key="key + i">
|
|
10
|
-
<span v-if="separator && i > 0" class="oc-kbd-sep">+</span>
|
|
11
|
-
<kbd class="oc-kbd">{{ key }}</kbd>
|
|
12
|
-
</template>
|
|
13
|
-
</slot>
|
|
14
|
-
</span>
|
|
15
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Shortcut: `keys` ["Ctrl", "K"] renders one key per entry, with a "+" separator when `separator` is set. Or pass OcKbd in the slot. Classes: .oc-kbd-group, .oc-kbd-sep */
|
|
3
|
+
defineProps<{ keys?: string[]; separator?: boolean }>()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<span class="oc-kbd-group">
|
|
8
|
+
<slot>
|
|
9
|
+
<template v-for="(key, i) in keys" :key="key + i">
|
|
10
|
+
<span v-if="separator && i > 0" class="oc-kbd-sep">+</span>
|
|
11
|
+
<kbd class="oc-kbd">{{ key }}</kbd>
|
|
12
|
+
</template>
|
|
13
|
+
</slot>
|
|
14
|
+
</span>
|
|
15
|
+
</template>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Form label. `for` links it to a control. Classes: .oc-label(--required/--inline/--muted) */
|
|
3
|
-
defineProps<{ for?: string; required?: boolean; inline?: boolean; muted?: boolean }>()
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<template>
|
|
7
|
-
<label :class="['oc-label', required && 'oc-label--required', inline && 'oc-label--inline', muted && 'oc-label--muted']" :for="$props.for">
|
|
8
|
-
<slot />
|
|
9
|
-
</label>
|
|
10
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Form label. `for` links it to a control. Classes: .oc-label(--required/--inline/--muted) */
|
|
3
|
+
defineProps<{ for?: string; required?: boolean; inline?: boolean; muted?: boolean }>()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<label :class="['oc-label', required && 'oc-label--required', inline && 'oc-label--inline', muted && 'oc-label--muted']" :for="$props.for">
|
|
8
|
+
<slot />
|
|
9
|
+
</label>
|
|
10
|
+
</template>
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcLanguageSelect.vue | Map: types + props -> template (label pill: icon slot + native select) -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Compact language / currency switcher. v-model = selected value. Slot `icon` replaces the globe / symbol.
|
|
5
|
-
* Same markup as packages/ui/snippets/language-select.html. class/style on the pill, other attributes on the select.
|
|
6
|
-
*/
|
|
7
|
-
import { useAttrsSplit } from '../composables/useAttrsSplit'
|
|
8
|
-
|
|
9
|
-
export interface LanguageSelectItem {
|
|
10
|
-
value: string
|
|
11
|
-
label: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
defineOptions({ inheritAttrs: false })
|
|
15
|
-
defineProps<{
|
|
16
|
-
options: LanguageSelectItem[]
|
|
17
|
-
/** aria-label of the select (default "Language" / "Currency") */
|
|
18
|
-
label?: string
|
|
19
|
-
currency?: boolean
|
|
20
|
-
ghost?: boolean
|
|
21
|
-
/** Text icon, e.g. "€" (default: globe, or € with `currency`) */
|
|
22
|
-
icon?: string
|
|
23
|
-
}>()
|
|
24
|
-
const model = defineModel<string>()
|
|
25
|
-
const { root, control } = useAttrsSplit()
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
|
-
<template>
|
|
29
|
-
<label v-bind="root" :class="['oc-language-select', ghost && 'oc-language-select--ghost', currency && 'oc-language-select--currency']">
|
|
30
|
-
<span class="oc-language-select-icon" aria-hidden="true">
|
|
31
|
-
<slot name="icon">
|
|
32
|
-
<template v-if="icon || currency">{{ icon ?? '€' }}</template>
|
|
33
|
-
<svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9" /><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" /></svg>
|
|
34
|
-
</slot>
|
|
35
|
-
</span>
|
|
36
|
-
<select v-bind="control" v-model="model" class="oc-language-select-control" :aria-label="label ?? (currency ? 'Currency' : 'Language')">
|
|
37
|
-
<option v-for="option in options" :key="option.value" :value="option.value">{{ option.label }}</option>
|
|
38
|
-
</select>
|
|
39
|
-
</label>
|
|
40
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcLanguageSelect.vue | Map: types + props -> template (label pill: icon slot + native select) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Compact language / currency switcher. v-model = selected value. Slot `icon` replaces the globe / symbol.
|
|
5
|
+
* Same markup as packages/ui/snippets/language-select.html. class/style on the pill, other attributes on the select.
|
|
6
|
+
*/
|
|
7
|
+
import { useAttrsSplit } from '../composables/useAttrsSplit'
|
|
8
|
+
|
|
9
|
+
export interface LanguageSelectItem {
|
|
10
|
+
value: string
|
|
11
|
+
label: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
defineOptions({ inheritAttrs: false })
|
|
15
|
+
defineProps<{
|
|
16
|
+
options: LanguageSelectItem[]
|
|
17
|
+
/** aria-label of the select (default "Language" / "Currency") */
|
|
18
|
+
label?: string
|
|
19
|
+
currency?: boolean
|
|
20
|
+
ghost?: boolean
|
|
21
|
+
/** Text icon, e.g. "€" (default: globe, or € with `currency`) */
|
|
22
|
+
icon?: string
|
|
23
|
+
}>()
|
|
24
|
+
const model = defineModel<string>()
|
|
25
|
+
const { root, control } = useAttrsSplit()
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<label v-bind="root" :class="['oc-language-select', ghost && 'oc-language-select--ghost', currency && 'oc-language-select--currency']">
|
|
30
|
+
<span class="oc-language-select-icon" aria-hidden="true">
|
|
31
|
+
<slot name="icon">
|
|
32
|
+
<template v-if="icon || currency">{{ icon ?? '€' }}</template>
|
|
33
|
+
<svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9" /><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" /></svg>
|
|
34
|
+
</slot>
|
|
35
|
+
</span>
|
|
36
|
+
<select v-bind="control" v-model="model" class="oc-language-select-control" :aria-label="label ?? (currency ? 'Currency' : 'Language')">
|
|
37
|
+
<option v-for="option in options" :key="option.value" :value="option.value">{{ option.label }}</option>
|
|
38
|
+
</select>
|
|
39
|
+
</label>
|
|
40
|
+
</template>
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Large clickable card with icon, title and description. Renders <a> with `href`, <span> when disabled,
|
|
4
|
-
* otherwise <button>. State: `current` (aria-current), `disabled` (aria-disabled).
|
|
5
|
-
* Slots: icon, title (+ trailing badge), description, footer.
|
|
6
|
-
*/
|
|
7
|
-
import { computed } from 'vue'
|
|
8
|
-
|
|
9
|
-
const props = defineProps<{
|
|
10
|
-
href?: string
|
|
11
|
-
title?: string
|
|
12
|
-
description?: string
|
|
13
|
-
footer?: string
|
|
14
|
-
current?: boolean
|
|
15
|
-
disabled?: boolean
|
|
16
|
-
}>()
|
|
17
|
-
const tag = computed(() => (props.disabled ? 'span' : props.href ? 'a' : 'button'))
|
|
18
|
-
</script>
|
|
19
|
-
|
|
20
|
-
<template>
|
|
21
|
-
<component
|
|
22
|
-
:is="tag"
|
|
23
|
-
class="oc-launcher"
|
|
24
|
-
:href="tag === 'a' ? href : undefined"
|
|
25
|
-
:type="tag === 'button' ? 'button' : undefined"
|
|
26
|
-
:aria-current="current ? 'true' : undefined"
|
|
27
|
-
:aria-disabled="disabled ? 'true' : undefined"
|
|
28
|
-
>
|
|
29
|
-
<span v-if="$slots.icon" class="oc-launcher-icon" aria-hidden="true"><slot name="icon" /></span>
|
|
30
|
-
<h3 class="oc-launcher-title"><slot name="title">{{ title }}</slot></h3>
|
|
31
|
-
<p v-if="description || $slots.description" class="oc-launcher-description"><slot name="description">{{ description }}</slot></p>
|
|
32
|
-
<span v-if="footer || $slots.footer" class="oc-launcher-footer"><slot name="footer">{{ footer }}</slot></span>
|
|
33
|
-
</component>
|
|
34
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Large clickable card with icon, title and description. Renders <a> with `href`, <span> when disabled,
|
|
4
|
+
* otherwise <button>. State: `current` (aria-current), `disabled` (aria-disabled).
|
|
5
|
+
* Slots: icon, title (+ trailing badge), description, footer.
|
|
6
|
+
*/
|
|
7
|
+
import { computed } from 'vue'
|
|
8
|
+
|
|
9
|
+
const props = defineProps<{
|
|
10
|
+
href?: string
|
|
11
|
+
title?: string
|
|
12
|
+
description?: string
|
|
13
|
+
footer?: string
|
|
14
|
+
current?: boolean
|
|
15
|
+
disabled?: boolean
|
|
16
|
+
}>()
|
|
17
|
+
const tag = computed(() => (props.disabled ? 'span' : props.href ? 'a' : 'button'))
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<component
|
|
22
|
+
:is="tag"
|
|
23
|
+
class="oc-launcher"
|
|
24
|
+
:href="tag === 'a' ? href : undefined"
|
|
25
|
+
:type="tag === 'button' ? 'button' : undefined"
|
|
26
|
+
:aria-current="current ? 'true' : undefined"
|
|
27
|
+
:aria-disabled="disabled ? 'true' : undefined"
|
|
28
|
+
>
|
|
29
|
+
<span v-if="$slots.icon" class="oc-launcher-icon" aria-hidden="true"><slot name="icon" /></span>
|
|
30
|
+
<h3 class="oc-launcher-title"><slot name="title">{{ title }}</slot></h3>
|
|
31
|
+
<p v-if="description || $slots.description" class="oc-launcher-description"><slot name="description">{{ description }}</slot></p>
|
|
32
|
+
<span v-if="footer || $slots.footer" class="oc-launcher-footer"><slot name="footer">{{ footer }}</slot></span>
|
|
33
|
+
</component>
|
|
34
|
+
</template>
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcLightbox.vue | Map: props + model -> open at photo -> thumbnails -> <dialog> (bar, track of slides) -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Lightbox on a native <dialog> (same markup as snippets/lightbox.html). v-model:open controls it.
|
|
5
|
-
* images: { src?, alt, caption?, thumb? }[] (no src = placeholder art). Prev / next are anchor links.
|
|
6
|
-
* Slot: caption (scoped: { image, index }). Classes: .oc-lightbox(--single), __bar, __title, __close, __track,
|
|
7
|
-
* __slide, __media, __nav(--prev/--next), __caption, __counter; .oc-lightbox-thumbs / -thumb.
|
|
8
|
-
*/
|
|
9
|
-
import { nextTick, ref } from 'vue'
|
|
10
|
-
import { useDialog } from '../composables/useDialog'
|
|
11
|
-
import { useUid } from '../composables/useUid'
|
|
12
|
-
|
|
13
|
-
const props = withDefaults(
|
|
14
|
-
defineProps<{ images: { src?: string; alt: string; caption?: string; thumb?: string }[]; title?: string; label?: string; thumbnails?: boolean }>(),
|
|
15
|
-
{ label: 'Photo viewer', thumbnails: true },
|
|
16
|
-
)
|
|
17
|
-
const open = defineModel<boolean>('open', { default: false })
|
|
18
|
-
const el = ref<HTMLDialogElement | null>(null)
|
|
19
|
-
const { onClose } = useDialog(open, el)
|
|
20
|
-
const base = useUid('oc-lightbox')
|
|
21
|
-
const n = () => props.images.length
|
|
22
|
-
const slideId = (i: number) => `${base}-${((i % n()) + n()) % n() + 1}`
|
|
23
|
-
|
|
24
|
-
// Open the viewer and jump (without animation) to photo i.
|
|
25
|
-
async function show(i: number) {
|
|
26
|
-
open.value = true
|
|
27
|
-
await nextTick()
|
|
28
|
-
document.getElementById(slideId(i))?.scrollIntoView?.({ behavior: 'instant' as ScrollBehavior, block: 'nearest', inline: 'center' })
|
|
29
|
-
}
|
|
30
|
-
defineExpose({ show, close: () => (open.value = false) })
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
<template>
|
|
34
|
-
<div v-if="thumbnails" class="oc-lightbox-thumbs">
|
|
35
|
-
<button v-for="(img, i) in images" :key="i" type="button" class="oc-lightbox-thumb" :aria-label="`Open photo ${i + 1}: ${img.alt}`" @click="show(i)">
|
|
36
|
-
<img v-if="img.thumb || img.src" :src="img.thumb || img.src" :alt="img.alt" />
|
|
37
|
-
<span v-else class="oc-media-art" />
|
|
38
|
-
</button>
|
|
39
|
-
</div>
|
|
40
|
-
<dialog ref="el" :class="['oc-lightbox', images.length === 1 && 'oc-lightbox--single']" closedby="any" :aria-label="label" @close="onClose">
|
|
41
|
-
<div class="oc-lightbox__bar">
|
|
42
|
-
<h2 v-if="title" class="oc-lightbox__title">{{ title }}</h2>
|
|
43
|
-
<span v-else />
|
|
44
|
-
<button type="button" class="oc-lightbox__close" aria-label="Close" @click="open = false">✕</button>
|
|
45
|
-
</div>
|
|
46
|
-
<div class="oc-lightbox__track" tabindex="0" aria-label="Photos, use the arrow keys">
|
|
47
|
-
<figure v-for="(img, i) in images" :id="slideId(i)" :key="i" class="oc-lightbox__slide">
|
|
48
|
-
<div class="oc-lightbox__media">
|
|
49
|
-
<img v-if="img.src" :src="img.src" :alt="img.alt" />
|
|
50
|
-
<span v-else class="oc-media-art" role="img" :aria-label="img.alt" />
|
|
51
|
-
</div>
|
|
52
|
-
<template v-if="images.length > 1">
|
|
53
|
-
<a class="oc-lightbox__nav oc-lightbox__nav--prev" :href="`#${slideId(i - 1)}`" aria-label="Previous photo">←</a>
|
|
54
|
-
<a class="oc-lightbox__nav oc-lightbox__nav--next" :href="`#${slideId(i + 1)}`" aria-label="Next photo">→</a>
|
|
55
|
-
</template>
|
|
56
|
-
<figcaption class="oc-lightbox__caption">
|
|
57
|
-
<span v-if="images.length > 1" class="oc-lightbox__counter">{{ i + 1 }} / {{ images.length }}</span>
|
|
58
|
-
<slot name="caption" :image="img" :index="i">{{ img.caption ?? img.alt }}</slot>
|
|
59
|
-
</figcaption>
|
|
60
|
-
</figure>
|
|
61
|
-
</div>
|
|
62
|
-
</dialog>
|
|
63
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcLightbox.vue | Map: props + model -> open at photo -> thumbnails -> <dialog> (bar, track of slides) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Lightbox on a native <dialog> (same markup as snippets/lightbox.html). v-model:open controls it.
|
|
5
|
+
* images: { src?, alt, caption?, thumb? }[] (no src = placeholder art). Prev / next are anchor links.
|
|
6
|
+
* Slot: caption (scoped: { image, index }). Classes: .oc-lightbox(--single), __bar, __title, __close, __track,
|
|
7
|
+
* __slide, __media, __nav(--prev/--next), __caption, __counter; .oc-lightbox-thumbs / -thumb.
|
|
8
|
+
*/
|
|
9
|
+
import { nextTick, ref } from 'vue'
|
|
10
|
+
import { useDialog } from '../composables/useDialog'
|
|
11
|
+
import { useUid } from '../composables/useUid'
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(
|
|
14
|
+
defineProps<{ images: { src?: string; alt: string; caption?: string; thumb?: string }[]; title?: string; label?: string; thumbnails?: boolean }>(),
|
|
15
|
+
{ label: 'Photo viewer', thumbnails: true },
|
|
16
|
+
)
|
|
17
|
+
const open = defineModel<boolean>('open', { default: false })
|
|
18
|
+
const el = ref<HTMLDialogElement | null>(null)
|
|
19
|
+
const { onClose } = useDialog(open, el)
|
|
20
|
+
const base = useUid('oc-lightbox')
|
|
21
|
+
const n = () => props.images.length
|
|
22
|
+
const slideId = (i: number) => `${base}-${((i % n()) + n()) % n() + 1}`
|
|
23
|
+
|
|
24
|
+
// Open the viewer and jump (without animation) to photo i.
|
|
25
|
+
async function show(i: number) {
|
|
26
|
+
open.value = true
|
|
27
|
+
await nextTick()
|
|
28
|
+
document.getElementById(slideId(i))?.scrollIntoView?.({ behavior: 'instant' as ScrollBehavior, block: 'nearest', inline: 'center' })
|
|
29
|
+
}
|
|
30
|
+
defineExpose({ show, close: () => (open.value = false) })
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div v-if="thumbnails" class="oc-lightbox-thumbs">
|
|
35
|
+
<button v-for="(img, i) in images" :key="i" type="button" class="oc-lightbox-thumb" :aria-label="`Open photo ${i + 1}: ${img.alt}`" @click="show(i)">
|
|
36
|
+
<img v-if="img.thumb || img.src" :src="img.thumb || img.src" :alt="img.alt" />
|
|
37
|
+
<span v-else class="oc-media-art" />
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
<dialog ref="el" :class="['oc-lightbox', images.length === 1 && 'oc-lightbox--single']" closedby="any" :aria-label="label" @close="onClose">
|
|
41
|
+
<div class="oc-lightbox__bar">
|
|
42
|
+
<h2 v-if="title" class="oc-lightbox__title">{{ title }}</h2>
|
|
43
|
+
<span v-else />
|
|
44
|
+
<button type="button" class="oc-lightbox__close" aria-label="Close" @click="open = false">✕</button>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="oc-lightbox__track" tabindex="0" aria-label="Photos, use the arrow keys">
|
|
47
|
+
<figure v-for="(img, i) in images" :id="slideId(i)" :key="i" class="oc-lightbox__slide">
|
|
48
|
+
<div class="oc-lightbox__media">
|
|
49
|
+
<img v-if="img.src" :src="img.src" :alt="img.alt" />
|
|
50
|
+
<span v-else class="oc-media-art" role="img" :aria-label="img.alt" />
|
|
51
|
+
</div>
|
|
52
|
+
<template v-if="images.length > 1">
|
|
53
|
+
<a class="oc-lightbox__nav oc-lightbox__nav--prev" :href="`#${slideId(i - 1)}`" aria-label="Previous photo">←</a>
|
|
54
|
+
<a class="oc-lightbox__nav oc-lightbox__nav--next" :href="`#${slideId(i + 1)}`" aria-label="Next photo">→</a>
|
|
55
|
+
</template>
|
|
56
|
+
<figcaption class="oc-lightbox__caption">
|
|
57
|
+
<span v-if="images.length > 1" class="oc-lightbox__counter">{{ i + 1 }} / {{ images.length }}</span>
|
|
58
|
+
<slot name="caption" :image="img" :index="i">{{ img.caption ?? img.alt }}</slot>
|
|
59
|
+
</figcaption>
|
|
60
|
+
</figure>
|
|
61
|
+
</div>
|
|
62
|
+
</dialog>
|
|
63
|
+
</template>
|