@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,21 +1,21 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Aspect-ratio box. `ratio` is a preset (square | photo | portrait | wide), a number (1.5) or a CSS ratio ("5 / 2");
|
|
4
|
-
* default 16 / 9. `center` centers the content. Classes: .oc-aspect(--square/--photo/--portrait/--wide/--center), --oc-aspect
|
|
5
|
-
*/
|
|
6
|
-
import { computed } from 'vue'
|
|
7
|
-
|
|
8
|
-
const presets = ['square', 'photo', 'portrait', 'wide']
|
|
9
|
-
const props = defineProps<{ ratio?: 'square' | 'photo' | 'portrait' | 'wide' | number | string; center?: boolean }>()
|
|
10
|
-
const preset = computed(() => (typeof props.ratio === 'string' && presets.includes(props.ratio) ? props.ratio : undefined))
|
|
11
|
-
const custom = computed(() => (props.ratio !== undefined && !preset.value ? String(props.ratio) : undefined))
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<template>
|
|
15
|
-
<div
|
|
16
|
-
:class="['oc-aspect', preset && `oc-aspect--${preset}`, center && 'oc-aspect--center']"
|
|
17
|
-
:style="custom ? { '--oc-aspect': custom } : undefined"
|
|
18
|
-
>
|
|
19
|
-
<slot />
|
|
20
|
-
</div>
|
|
21
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Aspect-ratio box. `ratio` is a preset (square | photo | portrait | wide), a number (1.5) or a CSS ratio ("5 / 2");
|
|
4
|
+
* default 16 / 9. `center` centers the content. Classes: .oc-aspect(--square/--photo/--portrait/--wide/--center), --oc-aspect
|
|
5
|
+
*/
|
|
6
|
+
import { computed } from 'vue'
|
|
7
|
+
|
|
8
|
+
const presets = ['square', 'photo', 'portrait', 'wide']
|
|
9
|
+
const props = defineProps<{ ratio?: 'square' | 'photo' | 'portrait' | 'wide' | number | string; center?: boolean }>()
|
|
10
|
+
const preset = computed(() => (typeof props.ratio === 'string' && presets.includes(props.ratio) ? props.ratio : undefined))
|
|
11
|
+
const custom = computed(() => (props.ratio !== undefined && !preset.value ? String(props.ratio) : undefined))
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div
|
|
16
|
+
:class="['oc-aspect', preset && `oc-aspect--${preset}`, center && 'oc-aspect--center']"
|
|
17
|
+
:style="custom ? { '--oc-aspect': custom } : undefined"
|
|
18
|
+
>
|
|
19
|
+
<slot />
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcAttachment.vue | Map: type / extension helpers -> props -> a[download] or div chip (icon, name, size, remove) -->
|
|
2
|
-
<script lang="ts">
|
|
3
|
-
export type AttachmentType = 'pdf' | 'doc' | 'sheet' | 'zip' | 'image'
|
|
4
|
-
export interface AttachmentFile {
|
|
5
|
-
name: string
|
|
6
|
-
size?: string
|
|
7
|
-
type?: AttachmentType
|
|
8
|
-
ext?: string
|
|
9
|
-
href?: string
|
|
10
|
-
image?: string
|
|
11
|
-
state?: 'uploading' | 'error'
|
|
12
|
-
progress?: number
|
|
13
|
-
removable?: boolean
|
|
14
|
-
removeLabel?: string
|
|
15
|
-
}
|
|
16
|
-
const TYPES: Record<string, AttachmentType> = {
|
|
17
|
-
pdf: 'pdf', doc: 'doc', docx: 'doc', odt: 'doc', rtf: 'doc', txt: 'doc', xls: 'sheet', xlsx: 'sheet', csv: 'sheet', ods: 'sheet',
|
|
18
|
-
zip: 'zip', rar: 'zip', '7z': 'zip', gz: 'zip', jpg: 'image', jpeg: 'image', png: 'image', gif: 'image', webp: 'image', heic: 'image', avif: 'image',
|
|
19
|
-
}
|
|
20
|
-
const extOf = (name: string) => (name.includes('.') ? name.split('.').pop()!.toLowerCase() : '')
|
|
21
|
-
/** Icon text and tint of a file name (Plan.xlsx -> XLS, sheet). */
|
|
22
|
-
export const attachmentMeta = (f: AttachmentFile) => ({
|
|
23
|
-
ext: f.ext ?? extOf(f.name).toUpperCase().replace(/^(DOC|XLS|PPT)X$/, '$1'),
|
|
24
|
-
type: f.type ?? TYPES[extOf(f.name)],
|
|
25
|
-
})
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
|
-
<script setup lang="ts">
|
|
29
|
-
/**
|
|
30
|
-
* File chip (same markup as snippets/attachment.html): icon tile by type, name, size, upload / error state and remove button.
|
|
31
|
-
* With href (and not removable) it is an <a download>. Emits: remove. Classes: .oc-attachment(--pdf/--doc/--sheet/--zip/--image,
|
|
32
|
-
* --uploading/--error) __icon __body __name __size __remove; --oc-attachment-progress while uploading.
|
|
33
|
-
*/
|
|
34
|
-
import { computed } from 'vue'
|
|
35
|
-
|
|
36
|
-
const props = withDefaults(defineProps<AttachmentFile>(), { progress: 0 })
|
|
37
|
-
const emit = defineEmits<{ remove: [] }>()
|
|
38
|
-
const meta = computed(() => attachmentMeta(props))
|
|
39
|
-
const uploading = computed(() => props.state === 'uploading')
|
|
40
|
-
const link = computed(() => !!props.href && !props.removable)
|
|
41
|
-
</script>
|
|
42
|
-
|
|
43
|
-
<template>
|
|
44
|
-
<component
|
|
45
|
-
:is="link ? 'a' : 'div'"
|
|
46
|
-
:class="['oc-attachment', meta.type && `oc-attachment--${meta.type}`, state && `oc-attachment--${state}`]"
|
|
47
|
-
:href="link ? href : undefined"
|
|
48
|
-
:download="link ? '' : undefined"
|
|
49
|
-
:style="uploading ? `--oc-attachment-progress:${progress}%` : undefined"
|
|
50
|
-
>
|
|
51
|
-
<span class="oc-attachment__icon" aria-hidden="true"><img v-if="image" :src="image" alt="" /><template v-else>{{ meta.ext }}</template></span>
|
|
52
|
-
<span class="oc-attachment__body">
|
|
53
|
-
<span class="oc-attachment__name">{{ name }}</span>
|
|
54
|
-
<span v-if="uploading" class="oc-attachment__size" role="status">{{ size ?? `Uploading… ${progress}%` }}</span>
|
|
55
|
-
<span v-else-if="size" class="oc-attachment__size">{{ size }}</span>
|
|
56
|
-
</span>
|
|
57
|
-
<button v-if="removable" type="button" class="oc-attachment__remove" :aria-label="removeLabel ?? (uploading ? `Cancel upload of ${name}` : `Remove ${name}`)" @click="emit('remove')">✕</button>
|
|
58
|
-
</component>
|
|
59
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcAttachment.vue | Map: type / extension helpers -> props -> a[download] or div chip (icon, name, size, remove) -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
export type AttachmentType = 'pdf' | 'doc' | 'sheet' | 'zip' | 'image'
|
|
4
|
+
export interface AttachmentFile {
|
|
5
|
+
name: string
|
|
6
|
+
size?: string
|
|
7
|
+
type?: AttachmentType
|
|
8
|
+
ext?: string
|
|
9
|
+
href?: string
|
|
10
|
+
image?: string
|
|
11
|
+
state?: 'uploading' | 'error'
|
|
12
|
+
progress?: number
|
|
13
|
+
removable?: boolean
|
|
14
|
+
removeLabel?: string
|
|
15
|
+
}
|
|
16
|
+
const TYPES: Record<string, AttachmentType> = {
|
|
17
|
+
pdf: 'pdf', doc: 'doc', docx: 'doc', odt: 'doc', rtf: 'doc', txt: 'doc', xls: 'sheet', xlsx: 'sheet', csv: 'sheet', ods: 'sheet',
|
|
18
|
+
zip: 'zip', rar: 'zip', '7z': 'zip', gz: 'zip', jpg: 'image', jpeg: 'image', png: 'image', gif: 'image', webp: 'image', heic: 'image', avif: 'image',
|
|
19
|
+
}
|
|
20
|
+
const extOf = (name: string) => (name.includes('.') ? name.split('.').pop()!.toLowerCase() : '')
|
|
21
|
+
/** Icon text and tint of a file name (Plan.xlsx -> XLS, sheet). */
|
|
22
|
+
export const attachmentMeta = (f: AttachmentFile) => ({
|
|
23
|
+
ext: f.ext ?? extOf(f.name).toUpperCase().replace(/^(DOC|XLS|PPT)X$/, '$1'),
|
|
24
|
+
type: f.type ?? TYPES[extOf(f.name)],
|
|
25
|
+
})
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
/**
|
|
30
|
+
* File chip (same markup as snippets/attachment.html): icon tile by type, name, size, upload / error state and remove button.
|
|
31
|
+
* With href (and not removable) it is an <a download>. Emits: remove. Classes: .oc-attachment(--pdf/--doc/--sheet/--zip/--image,
|
|
32
|
+
* --uploading/--error) __icon __body __name __size __remove; --oc-attachment-progress while uploading.
|
|
33
|
+
*/
|
|
34
|
+
import { computed } from 'vue'
|
|
35
|
+
|
|
36
|
+
const props = withDefaults(defineProps<AttachmentFile>(), { progress: 0 })
|
|
37
|
+
const emit = defineEmits<{ remove: [] }>()
|
|
38
|
+
const meta = computed(() => attachmentMeta(props))
|
|
39
|
+
const uploading = computed(() => props.state === 'uploading')
|
|
40
|
+
const link = computed(() => !!props.href && !props.removable)
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<component
|
|
45
|
+
:is="link ? 'a' : 'div'"
|
|
46
|
+
:class="['oc-attachment', meta.type && `oc-attachment--${meta.type}`, state && `oc-attachment--${state}`]"
|
|
47
|
+
:href="link ? href : undefined"
|
|
48
|
+
:download="link ? '' : undefined"
|
|
49
|
+
:style="uploading ? `--oc-attachment-progress:${progress}%` : undefined"
|
|
50
|
+
>
|
|
51
|
+
<span class="oc-attachment__icon" aria-hidden="true"><img v-if="image" :src="image" alt="" /><template v-else>{{ meta.ext }}</template></span>
|
|
52
|
+
<span class="oc-attachment__body">
|
|
53
|
+
<span class="oc-attachment__name">{{ name }}</span>
|
|
54
|
+
<span v-if="uploading" class="oc-attachment__size" role="status">{{ size ?? `Uploading… ${progress}%` }}</span>
|
|
55
|
+
<span v-else-if="size" class="oc-attachment__size">{{ size }}</span>
|
|
56
|
+
</span>
|
|
57
|
+
<button v-if="removable" type="button" class="oc-attachment__remove" :aria-label="removeLabel ?? (uploading ? `Cancel upload of ${name}` : `Remove ${name}`)" @click="emit('remove')">✕</button>
|
|
58
|
+
</component>
|
|
59
|
+
</template>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcAttachmentImage.vue | Map: props -> a.oc-attachment-image (img or placeholder art, caption) -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/** Image preview inside a message (4 : 3, same markup as snippets/attachment.html). No src = placeholder art. Classes: .oc-attachment-image, __caption */
|
|
4
|
-
defineProps<{ href?: string; src?: string; alt: string; art?: number; caption?: string }>()
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<template>
|
|
8
|
-
<a class="oc-attachment-image" :href="href">
|
|
9
|
-
<img v-if="src" :src="src" :alt="alt" />
|
|
10
|
-
<span v-else :class="['oc-media-art', art != null && `oc-media-art--${art}`]" role="img" :aria-label="alt"></span>
|
|
11
|
-
<span v-if="caption" class="oc-attachment-image__caption">{{ caption }}</span>
|
|
12
|
-
</a>
|
|
13
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcAttachmentImage.vue | Map: props -> a.oc-attachment-image (img or placeholder art, caption) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/** Image preview inside a message (4 : 3, same markup as snippets/attachment.html). No src = placeholder art. Classes: .oc-attachment-image, __caption */
|
|
4
|
+
defineProps<{ href?: string; src?: string; alt: string; art?: number; caption?: string }>()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<a class="oc-attachment-image" :href="href">
|
|
9
|
+
<img v-if="src" :src="src" :alt="alt" />
|
|
10
|
+
<span v-else :class="['oc-media-art', art != null && `oc-media-art--${art}`]" role="img" :aria-label="alt"></span>
|
|
11
|
+
<span v-if="caption" class="oc-attachment-image__caption">{{ caption }}</span>
|
|
12
|
+
</a>
|
|
13
|
+
</template>
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcAttachmentList.vue | Map: props + demo files -> ul (li > chip) or div of chips -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Wrapping row of file chips (snippets/attachment.html). items = AttachmentFile[] (demo files when empty); slot default replaces them.
|
|
5
|
-
* stack = one per line. as = ul (default) or div. Emits: remove(file, index). Classes: .oc-attachment-list(--stack)
|
|
6
|
-
*/
|
|
7
|
-
import OcAttachment, { type AttachmentFile } from './OcAttachment.vue'
|
|
8
|
-
|
|
9
|
-
withDefaults(defineProps<{ items?: AttachmentFile[]; stack?: boolean; as?: 'ul' | 'div'; label?: string }>(), {
|
|
10
|
-
as: 'ul',
|
|
11
|
-
label: 'Files in this message',
|
|
12
|
-
items: () => [
|
|
13
|
-
{ name: 'Quote kitchen Dewitte v2.pdf', size: '1.2 MB', href: '#quote.pdf' },
|
|
14
|
-
{ name: 'Measurements ground floor.xlsx', size: '86 KB', href: '#measurements.xlsx' },
|
|
15
|
-
{ name: 'Contract addendum.docx', size: '240 KB', href: '#contract.docx' },
|
|
16
|
-
{ name: 'Plans architect.zip', size: '14.8 MB', href: '#plans.zip' },
|
|
17
|
-
],
|
|
18
|
-
})
|
|
19
|
-
const emit = defineEmits<{ remove: [file: AttachmentFile, index: number] }>()
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<template>
|
|
23
|
-
<component :is="as" :class="['oc-attachment-list', stack && 'oc-attachment-list--stack']" :aria-label="label">
|
|
24
|
-
<slot>
|
|
25
|
-
<template v-for="(f, i) in items" :key="i">
|
|
26
|
-
<li v-if="as === 'ul'"><OcAttachment v-bind="f" @remove="emit('remove', f, i)" /></li>
|
|
27
|
-
<OcAttachment v-else v-bind="f" @remove="emit('remove', f, i)" />
|
|
28
|
-
</template>
|
|
29
|
-
</slot>
|
|
30
|
-
</component>
|
|
31
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcAttachmentList.vue | Map: props + demo files -> ul (li > chip) or div of chips -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Wrapping row of file chips (snippets/attachment.html). items = AttachmentFile[] (demo files when empty); slot default replaces them.
|
|
5
|
+
* stack = one per line. as = ul (default) or div. Emits: remove(file, index). Classes: .oc-attachment-list(--stack)
|
|
6
|
+
*/
|
|
7
|
+
import OcAttachment, { type AttachmentFile } from './OcAttachment.vue'
|
|
8
|
+
|
|
9
|
+
withDefaults(defineProps<{ items?: AttachmentFile[]; stack?: boolean; as?: 'ul' | 'div'; label?: string }>(), {
|
|
10
|
+
as: 'ul',
|
|
11
|
+
label: 'Files in this message',
|
|
12
|
+
items: () => [
|
|
13
|
+
{ name: 'Quote kitchen Dewitte v2.pdf', size: '1.2 MB', href: '#quote.pdf' },
|
|
14
|
+
{ name: 'Measurements ground floor.xlsx', size: '86 KB', href: '#measurements.xlsx' },
|
|
15
|
+
{ name: 'Contract addendum.docx', size: '240 KB', href: '#contract.docx' },
|
|
16
|
+
{ name: 'Plans architect.zip', size: '14.8 MB', href: '#plans.zip' },
|
|
17
|
+
],
|
|
18
|
+
})
|
|
19
|
+
const emit = defineEmits<{ remove: [file: AttachmentFile, index: number] }>()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<component :is="as" :class="['oc-attachment-list', stack && 'oc-attachment-list--stack']" :aria-label="label">
|
|
24
|
+
<slot>
|
|
25
|
+
<template v-for="(f, i) in items" :key="i">
|
|
26
|
+
<li v-if="as === 'ul'"><OcAttachment v-bind="f" @remove="emit('remove', f, i)" /></li>
|
|
27
|
+
<OcAttachment v-else v-bind="f" @remove="emit('remove', f, i)" />
|
|
28
|
+
</template>
|
|
29
|
+
</slot>
|
|
30
|
+
</component>
|
|
31
|
+
</template>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcAttachmentThumb.vue | Map: props -> .oc-attachment-thumb (img or placeholder art, remove button) -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/** Square thumbnail of a pasted screenshot (snippets/attachment.html). removeLabel adds the remove button. Emits: remove. */
|
|
4
|
-
withDefaults(defineProps<{ src?: string; alt: string; art?: number; removeLabel?: string; as?: 'div' | 'span' }>(), { as: 'div' })
|
|
5
|
-
const emit = defineEmits<{ remove: [] }>()
|
|
6
|
-
</script>
|
|
7
|
-
|
|
8
|
-
<template>
|
|
9
|
-
<component :is="as" class="oc-attachment-thumb">
|
|
10
|
-
<img v-if="src" :src="src" :alt="alt" />
|
|
11
|
-
<span v-else :class="['oc-media-art', art != null && `oc-media-art--${art}`]" role="img" :aria-label="alt"></span>
|
|
12
|
-
<button v-if="removeLabel" type="button" class="oc-attachment__remove" :aria-label="removeLabel" @click="emit('remove')">✕</button>
|
|
13
|
-
</component>
|
|
14
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcAttachmentThumb.vue | Map: props -> .oc-attachment-thumb (img or placeholder art, remove button) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/** Square thumbnail of a pasted screenshot (snippets/attachment.html). removeLabel adds the remove button. Emits: remove. */
|
|
4
|
+
withDefaults(defineProps<{ src?: string; alt: string; art?: number; removeLabel?: string; as?: 'div' | 'span' }>(), { as: 'div' })
|
|
5
|
+
const emit = defineEmits<{ remove: [] }>()
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<component :is="as" class="oc-attachment-thumb">
|
|
10
|
+
<img v-if="src" :src="src" :alt="alt" />
|
|
11
|
+
<span v-else :class="['oc-media-art', art != null && `oc-media-art--${art}`]" role="img" :aria-label="alt"></span>
|
|
12
|
+
<button v-if="removeLabel" type="button" class="oc-attachment__remove" :aria-label="removeLabel" @click="emit('remove')">✕</button>
|
|
13
|
+
</component>
|
|
14
|
+
</template>
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Avatar (initials via the default slot, or an image via `src`). Classes: .oc-avatar--sm/lg/xl */
|
|
3
|
-
withDefaults(
|
|
4
|
-
defineProps<{
|
|
5
|
-
size?: 'sm' | 'md' | 'lg' | 'xl'
|
|
6
|
-
src?: string
|
|
7
|
-
alt?: string
|
|
8
|
-
}>(),
|
|
9
|
-
{ size: 'md', alt: '' },
|
|
10
|
-
)
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<template>
|
|
14
|
-
<span :class="['oc-avatar', size !== 'md' && `oc-avatar--${size}`]">
|
|
15
|
-
<img v-if="src" :src="src" :alt="alt" />
|
|
16
|
-
<slot v-else />
|
|
17
|
-
</span>
|
|
18
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Avatar (initials via the default slot, or an image via `src`). Classes: .oc-avatar--sm/lg/xl */
|
|
3
|
+
withDefaults(
|
|
4
|
+
defineProps<{
|
|
5
|
+
size?: 'sm' | 'md' | 'lg' | 'xl'
|
|
6
|
+
src?: string
|
|
7
|
+
alt?: string
|
|
8
|
+
}>(),
|
|
9
|
+
{ size: 'md', alt: '' },
|
|
10
|
+
)
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<span :class="['oc-avatar', size !== 'md' && `oc-avatar--${size}`]">
|
|
15
|
+
<img v-if="src" :src="src" :alt="alt" />
|
|
16
|
+
<slot v-else />
|
|
17
|
+
</span>
|
|
18
|
+
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Overlapping avatars. Put <OcAvatar> elements in the default slot. */
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<template>
|
|
6
|
-
<div class="oc-avatar-group"><slot /></div>
|
|
7
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Overlapping avatars. Put <OcAvatar> elements in the default slot. */
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div class="oc-avatar-group"><slot /></div>
|
|
7
|
+
</template>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Outline link that names its destination (Back to Products). */
|
|
3
|
-
withDefaults(defineProps<{ href?: string }>(), { href: '#' })
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<template>
|
|
7
|
-
<a class="oc-back-link" :href="href"><slot /></a>
|
|
8
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Outline link that names its destination (Back to Products). */
|
|
3
|
+
withDefaults(defineProps<{ href?: string }>(), { href: '#' })
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<a class="oc-back-link" :href="href"><slot /></a>
|
|
8
|
+
</template>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Badge. Classes: .oc-badge + --secondary/--outline/--destructive/--success/--warning/--info */
|
|
3
|
-
withDefaults(
|
|
4
|
-
defineProps<{
|
|
5
|
-
variant?: 'default' | 'secondary' | 'outline' | 'destructive' | 'success' | 'warning' | 'info'
|
|
6
|
-
}>(),
|
|
7
|
-
{ variant: 'default' },
|
|
8
|
-
)
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<template>
|
|
12
|
-
<span :class="['oc-badge', variant !== 'default' && `oc-badge--${variant}`]"><slot /></span>
|
|
13
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Badge. Classes: .oc-badge + --secondary/--outline/--destructive/--success/--warning/--info */
|
|
3
|
+
withDefaults(
|
|
4
|
+
defineProps<{
|
|
5
|
+
variant?: 'default' | 'secondary' | 'outline' | 'destructive' | 'success' | 'warning' | 'info'
|
|
6
|
+
}>(),
|
|
7
|
+
{ variant: 'default' },
|
|
8
|
+
)
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<span :class="['oc-badge', variant !== 'default' && `oc-badge--${variant}`]"><slot /></span>
|
|
13
|
+
</template>
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcBarcodeInput.vue | Map: props + model -> template (label, digits-only field, preview figure + slot) -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Barcode / EAN field with a preview. v-model: string (digits only).
|
|
5
|
-
* Slot `preview`: your own <svg>/<canvas> barcode; default = decorative placeholder bars.
|
|
6
|
-
* Same markup as packages/ui/snippets/barcode-input.html. class/style on the wrapper, other attributes on the input.
|
|
7
|
-
*/
|
|
8
|
-
import { computed } from 'vue'
|
|
9
|
-
import { useAttrsSplit } from '../composables/useAttrsSplit'
|
|
10
|
-
import { useUid } from '../composables/useUid'
|
|
11
|
-
|
|
12
|
-
defineOptions({ inheritAttrs: false })
|
|
13
|
-
const props = withDefaults(defineProps<{ label?: string; id?: string; emptyText?: string; error?: boolean }>(), { emptyText: 'No code yet' })
|
|
14
|
-
const model = defineModel<string>({ default: '' })
|
|
15
|
-
const { root, control } = useAttrsSplit()
|
|
16
|
-
const auto = useUid('oc-barcode')
|
|
17
|
-
const inputId = computed(() => props.id ?? auto)
|
|
18
|
-
|
|
19
|
-
function onInput(e: Event) {
|
|
20
|
-
const input = e.target as HTMLInputElement
|
|
21
|
-
model.value = input.value
|
|
22
|
-
}
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
<template>
|
|
26
|
-
<div v-bind="root" class="oc-barcode-input">
|
|
27
|
-
<label v-if="label" class="oc-field-label" :for="inputId">{{ label }}</label>
|
|
28
|
-
<div class="oc-input-group oc-input-group--affix">
|
|
29
|
-
<span class="oc-input-addon" aria-hidden="true">
|
|
30
|
-
<svg viewBox="0 0 16 16"><path fill="currentColor" d="M1 1h1.4v14H1zm2.2 0h.7v14h-.7zm1.6 0h1.5v14H4.8zm2.4 0h.7v14H7.2zm1.6 0h.7v14H8.8zm1.5 0h1.4v14h-1.4zm2.2 0H15v14h-.9z" /></svg>
|
|
31
|
-
</span>
|
|
32
|
-
<input
|
|
33
|
-
v-bind="control"
|
|
34
|
-
:id="inputId"
|
|
35
|
-
class="oc-input oc-barcode-input-field"
|
|
36
|
-
type="text"
|
|
37
|
-
autocomplete="off"
|
|
38
|
-
spellcheck="false"
|
|
39
|
-
maxlength="80"
|
|
40
|
-
placeholder="EAN-13 or internal code"
|
|
41
|
-
:aria-invalid="error ? 'true' : undefined"
|
|
42
|
-
:value="model"
|
|
43
|
-
@input="onInput"
|
|
44
|
-
/>
|
|
45
|
-
</div>
|
|
46
|
-
<figure :class="['oc-barcode-preview', !model && 'oc-barcode-preview--empty']" aria-label="Barcode preview">
|
|
47
|
-
<slot name="preview"><div class="oc-barcode-preview-bars" aria-hidden="true"></div></slot>
|
|
48
|
-
<figcaption class="oc-barcode-preview-code">{{ model || emptyText }}</figcaption>
|
|
49
|
-
</figure>
|
|
50
|
-
</div>
|
|
51
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcBarcodeInput.vue | Map: props + model -> template (label, digits-only field, preview figure + slot) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Barcode / EAN field with a preview. v-model: string (digits only).
|
|
5
|
+
* Slot `preview`: your own <svg>/<canvas> barcode; default = decorative placeholder bars.
|
|
6
|
+
* Same markup as packages/ui/snippets/barcode-input.html. class/style on the wrapper, other attributes on the input.
|
|
7
|
+
*/
|
|
8
|
+
import { computed } from 'vue'
|
|
9
|
+
import { useAttrsSplit } from '../composables/useAttrsSplit'
|
|
10
|
+
import { useUid } from '../composables/useUid'
|
|
11
|
+
|
|
12
|
+
defineOptions({ inheritAttrs: false })
|
|
13
|
+
const props = withDefaults(defineProps<{ label?: string; id?: string; emptyText?: string; error?: boolean }>(), { emptyText: 'No code yet' })
|
|
14
|
+
const model = defineModel<string>({ default: '' })
|
|
15
|
+
const { root, control } = useAttrsSplit()
|
|
16
|
+
const auto = useUid('oc-barcode')
|
|
17
|
+
const inputId = computed(() => props.id ?? auto)
|
|
18
|
+
|
|
19
|
+
function onInput(e: Event) {
|
|
20
|
+
const input = e.target as HTMLInputElement
|
|
21
|
+
model.value = input.value
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<div v-bind="root" class="oc-barcode-input">
|
|
27
|
+
<label v-if="label" class="oc-field-label" :for="inputId">{{ label }}</label>
|
|
28
|
+
<div class="oc-input-group oc-input-group--affix">
|
|
29
|
+
<span class="oc-input-addon" aria-hidden="true">
|
|
30
|
+
<svg viewBox="0 0 16 16"><path fill="currentColor" d="M1 1h1.4v14H1zm2.2 0h.7v14h-.7zm1.6 0h1.5v14H4.8zm2.4 0h.7v14H7.2zm1.6 0h.7v14H8.8zm1.5 0h1.4v14h-1.4zm2.2 0H15v14h-.9z" /></svg>
|
|
31
|
+
</span>
|
|
32
|
+
<input
|
|
33
|
+
v-bind="control"
|
|
34
|
+
:id="inputId"
|
|
35
|
+
class="oc-input oc-barcode-input-field"
|
|
36
|
+
type="text"
|
|
37
|
+
autocomplete="off"
|
|
38
|
+
spellcheck="false"
|
|
39
|
+
maxlength="80"
|
|
40
|
+
placeholder="EAN-13 or internal code"
|
|
41
|
+
:aria-invalid="error ? 'true' : undefined"
|
|
42
|
+
:value="model"
|
|
43
|
+
@input="onInput"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
<figure :class="['oc-barcode-preview', !model && 'oc-barcode-preview--empty']" aria-label="Barcode preview">
|
|
47
|
+
<slot name="preview"><div class="oc-barcode-preview-bars" aria-hidden="true"></div></slot>
|
|
48
|
+
<figcaption class="oc-barcode-preview-code">{{ model || emptyText }}</figcaption>
|
|
49
|
+
</figure>
|
|
50
|
+
</div>
|
|
51
|
+
</template>
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Breadcrumb. Pass `items` (the last one is the current page) or use the default slot. */
|
|
3
|
-
export interface BreadcrumbItem {
|
|
4
|
-
label: string
|
|
5
|
-
href?: string
|
|
6
|
-
}
|
|
7
|
-
withDefaults(defineProps<{ items?: BreadcrumbItem[]; label?: string }>(), {
|
|
8
|
-
items: () => [],
|
|
9
|
-
label: 'Breadcrumb',
|
|
10
|
-
})
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<template>
|
|
14
|
-
<nav class="oc-breadcrumb" :aria-label="label">
|
|
15
|
-
<slot>
|
|
16
|
-
<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">{{ item.label }}</a>
|
|
18
|
-
<span v-else class="oc-breadcrumb-item" aria-current="page">{{ item.label }}</span>
|
|
19
|
-
</template>
|
|
20
|
-
</slot>
|
|
21
|
-
</nav>
|
|
22
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Breadcrumb. Pass `items` (the last one is the current page) or use the default slot. */
|
|
3
|
+
export interface BreadcrumbItem {
|
|
4
|
+
label: string
|
|
5
|
+
href?: string
|
|
6
|
+
}
|
|
7
|
+
withDefaults(defineProps<{ items?: BreadcrumbItem[]; label?: string }>(), {
|
|
8
|
+
items: () => [],
|
|
9
|
+
label: 'Breadcrumb',
|
|
10
|
+
})
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<nav class="oc-breadcrumb" :aria-label="label">
|
|
15
|
+
<slot>
|
|
16
|
+
<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">{{ item.label }}</a>
|
|
18
|
+
<span v-else class="oc-breadcrumb-item" aria-current="page">{{ item.label }}</span>
|
|
19
|
+
</template>
|
|
20
|
+
</slot>
|
|
21
|
+
</nav>
|
|
22
|
+
</template>
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcBrowserFrame.vue | Map: props -> figure -> bar (dots, address, actions) -> view (slot or img) -> caption -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Fake browser chrome (same markup as snippets/browser-frame.html).
|
|
5
|
-
* Props: url (address pill), src / alt (screenshot), caption, minimal, dark, tilt, auto (free height), ratio ("16 / 9").
|
|
6
|
-
* Slots: default (live content, replaces the screenshot), actions (glyphs right in the bar), caption.
|
|
7
|
-
* Classes: .oc-browser-frame(--minimal/--dark/--tilt/--auto), __bar, __address, __actions, __view, __caption
|
|
8
|
-
*/
|
|
9
|
-
withDefaults(
|
|
10
|
-
defineProps<{ url?: string; src?: string; alt?: string; caption?: string; minimal?: boolean; dark?: boolean; tilt?: boolean; auto?: boolean; ratio?: string }>(),
|
|
11
|
-
{ alt: '' },
|
|
12
|
-
)
|
|
13
|
-
</script>
|
|
14
|
-
|
|
15
|
-
<template>
|
|
16
|
-
<figure
|
|
17
|
-
:class="['oc-browser-frame', minimal && 'oc-browser-frame--minimal', dark && 'oc-browser-frame--dark', tilt && 'oc-browser-frame--tilt', auto && 'oc-browser-frame--auto']"
|
|
18
|
-
:style="ratio ? { '--oc-browser-frame-ratio': ratio } : undefined"
|
|
19
|
-
>
|
|
20
|
-
<div class="oc-browser-frame__bar">
|
|
21
|
-
<i /><i /><i />
|
|
22
|
-
<span v-if="url" class="oc-browser-frame__address">{{ url }}</span>
|
|
23
|
-
<span v-if="$slots.actions" class="oc-browser-frame__actions" aria-hidden="true"><slot name="actions" /></span>
|
|
24
|
-
</div>
|
|
25
|
-
<div class="oc-browser-frame__view">
|
|
26
|
-
<slot>
|
|
27
|
-
<img v-if="src" :src="src" :alt="alt" />
|
|
28
|
-
<div v-else class="oc-media-art" role="img" :aria-label="alt" />
|
|
29
|
-
</slot>
|
|
30
|
-
</div>
|
|
31
|
-
<figcaption v-if="caption || $slots.caption" class="oc-browser-frame__caption"><slot name="caption">{{ caption }}</slot></figcaption>
|
|
32
|
-
</figure>
|
|
33
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcBrowserFrame.vue | Map: props -> figure -> bar (dots, address, actions) -> view (slot or img) -> caption -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Fake browser chrome (same markup as snippets/browser-frame.html).
|
|
5
|
+
* Props: url (address pill), src / alt (screenshot), caption, minimal, dark, tilt, auto (free height), ratio ("16 / 9").
|
|
6
|
+
* Slots: default (live content, replaces the screenshot), actions (glyphs right in the bar), caption.
|
|
7
|
+
* Classes: .oc-browser-frame(--minimal/--dark/--tilt/--auto), __bar, __address, __actions, __view, __caption
|
|
8
|
+
*/
|
|
9
|
+
withDefaults(
|
|
10
|
+
defineProps<{ url?: string; src?: string; alt?: string; caption?: string; minimal?: boolean; dark?: boolean; tilt?: boolean; auto?: boolean; ratio?: string }>(),
|
|
11
|
+
{ alt: '' },
|
|
12
|
+
)
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<figure
|
|
17
|
+
:class="['oc-browser-frame', minimal && 'oc-browser-frame--minimal', dark && 'oc-browser-frame--dark', tilt && 'oc-browser-frame--tilt', auto && 'oc-browser-frame--auto']"
|
|
18
|
+
:style="ratio ? { '--oc-browser-frame-ratio': ratio } : undefined"
|
|
19
|
+
>
|
|
20
|
+
<div class="oc-browser-frame__bar">
|
|
21
|
+
<i /><i /><i />
|
|
22
|
+
<span v-if="url" class="oc-browser-frame__address">{{ url }}</span>
|
|
23
|
+
<span v-if="$slots.actions" class="oc-browser-frame__actions" aria-hidden="true"><slot name="actions" /></span>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="oc-browser-frame__view">
|
|
26
|
+
<slot>
|
|
27
|
+
<img v-if="src" :src="src" :alt="alt" />
|
|
28
|
+
<div v-else class="oc-media-art" role="img" :aria-label="alt" />
|
|
29
|
+
</slot>
|
|
30
|
+
</div>
|
|
31
|
+
<figcaption v-if="caption || $slots.caption" class="oc-browser-frame__caption"><slot name="caption">{{ caption }}</slot></figcaption>
|
|
32
|
+
</figure>
|
|
33
|
+
</template>
|