@birdycom/omniaui-vue 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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,13 +1,13 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Icon, title, text and actions for lists without data. Slots: icon, title, default (text), actions. */
|
|
3
|
-
defineProps<{ icon?: string; title?: string; text?: string }>()
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<template>
|
|
7
|
-
<div class="oc-empty">
|
|
8
|
-
<span v-if="icon || $slots.icon" class="oc-empty-icon" aria-hidden="true"><slot name="icon">{{ icon }}</slot></span>
|
|
9
|
-
<h3 v-if="title || $slots.title" class="oc-empty-title"><slot name="title">{{ title }}</slot></h3>
|
|
10
|
-
<p v-if="text || $slots.default" class="oc-empty-text"><slot>{{ text }}</slot></p>
|
|
11
|
-
<div v-if="$slots.actions" class="oc-empty-actions"><slot name="actions" /></div>
|
|
12
|
-
</div>
|
|
13
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Icon, title, text and actions for lists without data. Slots: icon, title, default (text), actions. */
|
|
3
|
+
defineProps<{ icon?: string; title?: string; text?: string }>()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="oc-empty">
|
|
8
|
+
<span v-if="icon || $slots.icon" class="oc-empty-icon" aria-hidden="true"><slot name="icon">{{ icon }}</slot></span>
|
|
9
|
+
<h3 v-if="title || $slots.title" class="oc-empty-title"><slot name="title">{{ title }}</slot></h3>
|
|
10
|
+
<p v-if="text || $slots.default" class="oc-empty-text"><slot>{{ text }}</slot></p>
|
|
11
|
+
<div v-if="$slots.actions" class="oc-empty-actions"><slot name="actions" /></div>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcEntityCard.vue | Map: props -> article -> grid (frame | body: eyebrow, title, description, facts) -> footer -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Framed media cell + detail column (same markup as snippets/entity-card.html).
|
|
5
|
-
* Props: title, eyebrow, description, src / alt or glyph (frame content), contain, facts ([key, value][]), stacked, size (sm|md).
|
|
6
|
-
* Slots: media (frame content), default (extra body content), footer. Classes: .oc-entity-card(--stacked/--sm), __grid,
|
|
7
|
-
* __frame(--contain), __glyph, __body, __eyebrow, __title, __description, __facts, __footer
|
|
8
|
-
*/
|
|
9
|
-
withDefaults(
|
|
10
|
-
defineProps<{
|
|
11
|
-
title: string
|
|
12
|
-
eyebrow?: string
|
|
13
|
-
description?: string
|
|
14
|
-
src?: string
|
|
15
|
-
alt?: string
|
|
16
|
-
glyph?: string
|
|
17
|
-
contain?: boolean
|
|
18
|
-
facts?: [string, string][]
|
|
19
|
-
stacked?: boolean
|
|
20
|
-
size?: 'sm' | 'md'
|
|
21
|
-
}>(),
|
|
22
|
-
{ alt: '', size: 'md' },
|
|
23
|
-
)
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<template>
|
|
27
|
-
<article :class="['oc-entity-card', stacked && 'oc-entity-card--stacked', size === 'sm' && 'oc-entity-card--sm']">
|
|
28
|
-
<div class="oc-entity-card__grid">
|
|
29
|
-
<div :class="['oc-entity-card__frame', contain && 'oc-entity-card__frame--contain']">
|
|
30
|
-
<slot name="media">
|
|
31
|
-
<img v-if="src" :src="src" :alt="alt" />
|
|
32
|
-
<span v-else-if="glyph" class="oc-entity-card__glyph" aria-hidden="true">{{ glyph }}</span>
|
|
33
|
-
</slot>
|
|
34
|
-
</div>
|
|
35
|
-
<div class="oc-entity-card__body">
|
|
36
|
-
<p v-if="eyebrow" class="oc-entity-card__eyebrow">{{ eyebrow }}</p>
|
|
37
|
-
<h3 class="oc-entity-card__title">{{ title }}</h3>
|
|
38
|
-
<p v-if="description" class="oc-entity-card__description">{{ description }}</p>
|
|
39
|
-
<dl v-if="facts?.length" class="oc-entity-card__facts">
|
|
40
|
-
<template v-for="([k, v], i) in facts" :key="i"><dt>{{ k }}</dt><dd>{{ v }}</dd></template>
|
|
41
|
-
</dl>
|
|
42
|
-
<slot />
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
<div v-if="$slots.footer" class="oc-entity-card__footer"><slot name="footer" /></div>
|
|
46
|
-
</article>
|
|
47
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcEntityCard.vue | Map: props -> article -> grid (frame | body: eyebrow, title, description, facts) -> footer -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Framed media cell + detail column (same markup as snippets/entity-card.html).
|
|
5
|
+
* Props: title, eyebrow, description, src / alt or glyph (frame content), contain, facts ([key, value][]), stacked, size (sm|md).
|
|
6
|
+
* Slots: media (frame content), default (extra body content), footer. Classes: .oc-entity-card(--stacked/--sm), __grid,
|
|
7
|
+
* __frame(--contain), __glyph, __body, __eyebrow, __title, __description, __facts, __footer
|
|
8
|
+
*/
|
|
9
|
+
withDefaults(
|
|
10
|
+
defineProps<{
|
|
11
|
+
title: string
|
|
12
|
+
eyebrow?: string
|
|
13
|
+
description?: string
|
|
14
|
+
src?: string
|
|
15
|
+
alt?: string
|
|
16
|
+
glyph?: string
|
|
17
|
+
contain?: boolean
|
|
18
|
+
facts?: [string, string][]
|
|
19
|
+
stacked?: boolean
|
|
20
|
+
size?: 'sm' | 'md'
|
|
21
|
+
}>(),
|
|
22
|
+
{ alt: '', size: 'md' },
|
|
23
|
+
)
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<article :class="['oc-entity-card', stacked && 'oc-entity-card--stacked', size === 'sm' && 'oc-entity-card--sm']">
|
|
28
|
+
<div class="oc-entity-card__grid">
|
|
29
|
+
<div :class="['oc-entity-card__frame', contain && 'oc-entity-card__frame--contain']">
|
|
30
|
+
<slot name="media">
|
|
31
|
+
<img v-if="src" :src="src" :alt="alt" />
|
|
32
|
+
<span v-else-if="glyph" class="oc-entity-card__glyph" aria-hidden="true">{{ glyph }}</span>
|
|
33
|
+
</slot>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="oc-entity-card__body">
|
|
36
|
+
<p v-if="eyebrow" class="oc-entity-card__eyebrow">{{ eyebrow }}</p>
|
|
37
|
+
<h3 class="oc-entity-card__title">{{ title }}</h3>
|
|
38
|
+
<p v-if="description" class="oc-entity-card__description">{{ description }}</p>
|
|
39
|
+
<dl v-if="facts?.length" class="oc-entity-card__facts">
|
|
40
|
+
<template v-for="([k, v], i) in facts" :key="i"><dt>{{ k }}</dt><dd>{{ v }}</dd></template>
|
|
41
|
+
</dl>
|
|
42
|
+
<slot />
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div v-if="$slots.footer" class="oc-entity-card__footer"><slot name="footer" /></div>
|
|
46
|
+
</article>
|
|
47
|
+
</template>
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcFactList.vue | Map: props (items + modifiers) -> dl.oc-fact-list -> div > dt + dd -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Key / value product facts (variant of the definition list). Pass `items`, or your own <div><dt><dd> rows in the slot.
|
|
5
|
-
* Classes: .oc-fact-list (--plain, --compact, --stacked, --accent)
|
|
6
|
-
*/
|
|
7
|
-
export interface FactListRow {
|
|
8
|
-
label: string
|
|
9
|
-
value: string
|
|
10
|
-
}
|
|
11
|
-
withDefaults(
|
|
12
|
-
defineProps<{
|
|
13
|
-
items?: FactListRow[]
|
|
14
|
-
plain?: boolean
|
|
15
|
-
compact?: boolean
|
|
16
|
-
stacked?: boolean
|
|
17
|
-
accent?: boolean
|
|
18
|
-
}>(),
|
|
19
|
-
{ items: () => [] },
|
|
20
|
-
)
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<template>
|
|
24
|
-
<dl
|
|
25
|
-
:class="[
|
|
26
|
-
'oc-fact-list',
|
|
27
|
-
plain && 'oc-fact-list--plain',
|
|
28
|
-
compact && 'oc-fact-list--compact',
|
|
29
|
-
stacked && 'oc-fact-list--stacked',
|
|
30
|
-
accent && 'oc-fact-list--accent',
|
|
31
|
-
]"
|
|
32
|
-
>
|
|
33
|
-
<slot>
|
|
34
|
-
<div v-for="(row, i) in items" :key="i"><dt>{{ row.label }}</dt><dd>{{ row.value }}</dd></div>
|
|
35
|
-
</slot>
|
|
36
|
-
</dl>
|
|
37
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcFactList.vue | Map: props (items + modifiers) -> dl.oc-fact-list -> div > dt + dd -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Key / value product facts (variant of the definition list). Pass `items`, or your own <div><dt><dd> rows in the slot.
|
|
5
|
+
* Classes: .oc-fact-list (--plain, --compact, --stacked, --accent)
|
|
6
|
+
*/
|
|
7
|
+
export interface FactListRow {
|
|
8
|
+
label: string
|
|
9
|
+
value: string
|
|
10
|
+
}
|
|
11
|
+
withDefaults(
|
|
12
|
+
defineProps<{
|
|
13
|
+
items?: FactListRow[]
|
|
14
|
+
plain?: boolean
|
|
15
|
+
compact?: boolean
|
|
16
|
+
stacked?: boolean
|
|
17
|
+
accent?: boolean
|
|
18
|
+
}>(),
|
|
19
|
+
{ items: () => [] },
|
|
20
|
+
)
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<dl
|
|
25
|
+
:class="[
|
|
26
|
+
'oc-fact-list',
|
|
27
|
+
plain && 'oc-fact-list--plain',
|
|
28
|
+
compact && 'oc-fact-list--compact',
|
|
29
|
+
stacked && 'oc-fact-list--stacked',
|
|
30
|
+
accent && 'oc-fact-list--accent',
|
|
31
|
+
]"
|
|
32
|
+
>
|
|
33
|
+
<slot>
|
|
34
|
+
<div v-for="(row, i) in items" :key="i"><dt>{{ row.label }}</dt><dd>{{ row.value }}</dd></div>
|
|
35
|
+
</slot>
|
|
36
|
+
</dl>
|
|
37
|
+
</template>
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcFilterCounts.vue | Map: types + props -> template (fieldset radiogroup -> tile per view) -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* View filter tiles with counts (Active / Deleted / All). v-model = selected value.
|
|
5
|
-
* Same markup as packages/ui/snippets/filter-counts.html. Classes: .oc-filter-counts(--compact), -item(--success/--destructive/--muted), -value, -label, -hint
|
|
6
|
-
*/
|
|
7
|
-
import { useUid } from '../composables/useUid'
|
|
8
|
-
|
|
9
|
-
export interface FilterCountsItem {
|
|
10
|
-
value: string
|
|
11
|
-
label: string
|
|
12
|
-
count: number | string
|
|
13
|
-
hint?: string
|
|
14
|
-
/** Accent color of the tile; default primary. */
|
|
15
|
-
tone?: 'primary' | 'success' | 'destructive' | 'muted'
|
|
16
|
-
disabled?: boolean
|
|
17
|
-
}
|
|
18
|
-
defineProps<{
|
|
19
|
-
options: FilterCountsItem[]
|
|
20
|
-
/** aria-label of the group */
|
|
21
|
-
label?: string
|
|
22
|
-
compact?: boolean
|
|
23
|
-
name?: string
|
|
24
|
-
}>()
|
|
25
|
-
const model = defineModel<string>()
|
|
26
|
-
const uid = useUid('oc-filter')
|
|
27
|
-
</script>
|
|
28
|
-
|
|
29
|
-
<template>
|
|
30
|
-
<fieldset :class="['oc-filter-counts', compact && 'oc-filter-counts--compact']" role="radiogroup" :aria-label="label">
|
|
31
|
-
<label v-for="option in options" :key="option.value" :class="['oc-filter-counts-item', option.tone && option.tone !== 'primary' && `oc-filter-counts-item--${option.tone}`]">
|
|
32
|
-
<input v-model="model" type="radio" :name="name ?? uid" :value="option.value" :disabled="option.disabled" />
|
|
33
|
-
<span class="oc-filter-counts-value">{{ option.count }}</span>
|
|
34
|
-
<span class="oc-filter-counts-label">{{ option.label }}</span>
|
|
35
|
-
<span v-if="option.hint" class="oc-filter-counts-hint">{{ option.hint }}</span>
|
|
36
|
-
</label>
|
|
37
|
-
</fieldset>
|
|
38
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcFilterCounts.vue | Map: types + props -> template (fieldset radiogroup -> tile per view) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* View filter tiles with counts (Active / Deleted / All). v-model = selected value.
|
|
5
|
+
* Same markup as packages/ui/snippets/filter-counts.html. Classes: .oc-filter-counts(--compact), -item(--success/--destructive/--muted), -value, -label, -hint
|
|
6
|
+
*/
|
|
7
|
+
import { useUid } from '../composables/useUid'
|
|
8
|
+
|
|
9
|
+
export interface FilterCountsItem {
|
|
10
|
+
value: string
|
|
11
|
+
label: string
|
|
12
|
+
count: number | string
|
|
13
|
+
hint?: string
|
|
14
|
+
/** Accent color of the tile; default primary. */
|
|
15
|
+
tone?: 'primary' | 'success' | 'destructive' | 'muted'
|
|
16
|
+
disabled?: boolean
|
|
17
|
+
}
|
|
18
|
+
defineProps<{
|
|
19
|
+
options: FilterCountsItem[]
|
|
20
|
+
/** aria-label of the group */
|
|
21
|
+
label?: string
|
|
22
|
+
compact?: boolean
|
|
23
|
+
name?: string
|
|
24
|
+
}>()
|
|
25
|
+
const model = defineModel<string>()
|
|
26
|
+
const uid = useUid('oc-filter')
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<fieldset :class="['oc-filter-counts', compact && 'oc-filter-counts--compact']" role="radiogroup" :aria-label="label">
|
|
31
|
+
<label v-for="option in options" :key="option.value" :class="['oc-filter-counts-item', option.tone && option.tone !== 'primary' && `oc-filter-counts-item--${option.tone}`]">
|
|
32
|
+
<input v-model="model" type="radio" :name="name ?? uid" :value="option.value" :disabled="option.disabled" />
|
|
33
|
+
<span class="oc-filter-counts-value">{{ option.count }}</span>
|
|
34
|
+
<span class="oc-filter-counts-label">{{ option.label }}</span>
|
|
35
|
+
<span v-if="option.hint" class="oc-filter-counts-hint">{{ option.hint }}</span>
|
|
36
|
+
</label>
|
|
37
|
+
</fieldset>
|
|
38
|
+
</template>
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcFlipCard.vue | Map: props + v-model:flipped -> card -> checkbox (toggle) -> inner -> front / back faces -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* 3D flip card (same markup as snippets/flip-card.html). v-model:flipped. trigger: toggle (checkbox, default) |
|
|
5
|
-
* hover (--hover, tabindex 0) | manual (.is-flipped from the model only). Props: vertical, frontLabel, backLabel.
|
|
6
|
-
* Slots: front, back. Classes: .oc-flip-card(--hover/--vertical, .is-flipped), __toggle, __inner, __face(--front/--back),
|
|
7
|
-
* __title, __text, __flip
|
|
8
|
-
*/
|
|
9
|
-
import { useUid } from '../composables/useUid'
|
|
10
|
-
|
|
11
|
-
withDefaults(
|
|
12
|
-
defineProps<{ trigger?: 'toggle' | 'hover' | 'manual'; vertical?: boolean; frontLabel?: string; backLabel?: string }>(),
|
|
13
|
-
{ trigger: 'toggle', frontLabel: 'Show answer', backLabel: 'Back' },
|
|
14
|
-
)
|
|
15
|
-
const flipped = defineModel<boolean>('flipped', { default: false })
|
|
16
|
-
const id = useUid('oc-flip')
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<template>
|
|
20
|
-
<div
|
|
21
|
-
:class="['oc-flip-card', trigger === 'hover' && 'oc-flip-card--hover', vertical && 'oc-flip-card--vertical', trigger !== 'toggle' && flipped && 'is-flipped']"
|
|
22
|
-
:tabindex="trigger === 'hover' ? 0 : undefined"
|
|
23
|
-
>
|
|
24
|
-
<input v-if="trigger === 'toggle'" :id="id" v-model="flipped" type="checkbox" class="oc-flip-card__toggle" />
|
|
25
|
-
<div class="oc-flip-card__inner">
|
|
26
|
-
<div class="oc-flip-card__face oc-flip-card__face--front">
|
|
27
|
-
<slot name="front" />
|
|
28
|
-
<label v-if="trigger === 'toggle'" class="oc-flip-card__flip" :for="id">{{ frontLabel }}</label>
|
|
29
|
-
</div>
|
|
30
|
-
<div class="oc-flip-card__face oc-flip-card__face--back">
|
|
31
|
-
<slot name="back" />
|
|
32
|
-
<label v-if="trigger === 'toggle'" class="oc-flip-card__flip" :for="id">{{ backLabel }}</label>
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcFlipCard.vue | Map: props + v-model:flipped -> card -> checkbox (toggle) -> inner -> front / back faces -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* 3D flip card (same markup as snippets/flip-card.html). v-model:flipped. trigger: toggle (checkbox, default) |
|
|
5
|
+
* hover (--hover, tabindex 0) | manual (.is-flipped from the model only). Props: vertical, frontLabel, backLabel.
|
|
6
|
+
* Slots: front, back. Classes: .oc-flip-card(--hover/--vertical, .is-flipped), __toggle, __inner, __face(--front/--back),
|
|
7
|
+
* __title, __text, __flip
|
|
8
|
+
*/
|
|
9
|
+
import { useUid } from '../composables/useUid'
|
|
10
|
+
|
|
11
|
+
withDefaults(
|
|
12
|
+
defineProps<{ trigger?: 'toggle' | 'hover' | 'manual'; vertical?: boolean; frontLabel?: string; backLabel?: string }>(),
|
|
13
|
+
{ trigger: 'toggle', frontLabel: 'Show answer', backLabel: 'Back' },
|
|
14
|
+
)
|
|
15
|
+
const flipped = defineModel<boolean>('flipped', { default: false })
|
|
16
|
+
const id = useUid('oc-flip')
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div
|
|
21
|
+
:class="['oc-flip-card', trigger === 'hover' && 'oc-flip-card--hover', vertical && 'oc-flip-card--vertical', trigger !== 'toggle' && flipped && 'is-flipped']"
|
|
22
|
+
:tabindex="trigger === 'hover' ? 0 : undefined"
|
|
23
|
+
>
|
|
24
|
+
<input v-if="trigger === 'toggle'" :id="id" v-model="flipped" type="checkbox" class="oc-flip-card__toggle" />
|
|
25
|
+
<div class="oc-flip-card__inner">
|
|
26
|
+
<div class="oc-flip-card__face oc-flip-card__face--front">
|
|
27
|
+
<slot name="front" />
|
|
28
|
+
<label v-if="trigger === 'toggle'" class="oc-flip-card__flip" :for="id">{{ frontLabel }}</label>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="oc-flip-card__face oc-flip-card__face--back">
|
|
31
|
+
<slot name="back" />
|
|
32
|
+
<label v-if="trigger === 'toggle'" class="oc-flip-card__flip" :for="id">{{ backLabel }}</label>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Form field: label, control (default slot), description and error.
|
|
4
|
-
* The slot receives { id, describedBy, invalid } so the control can be wired:
|
|
5
|
-
* <OcFormField label="Email" description="..." v-slot="f"><OcInput :id="f.id" :aria-describedby="f.describedBy" /></OcFormField>
|
|
6
|
-
* Classes: .oc-field(--horizontal), .oc-field-label, .oc-field-description, .oc-field-error
|
|
7
|
-
*/
|
|
8
|
-
import { computed } from 'vue'
|
|
9
|
-
import { useUid } from '../composables/useUid'
|
|
10
|
-
|
|
11
|
-
const props = defineProps<{
|
|
12
|
-
label?: string
|
|
13
|
-
description?: string
|
|
14
|
-
error?: string
|
|
15
|
-
/** id of the control; generated when omitted */
|
|
16
|
-
for?: string
|
|
17
|
-
horizontal?: boolean
|
|
18
|
-
}>()
|
|
19
|
-
const generated = useUid('oc-field')
|
|
20
|
-
const id = computed(() => props.for ?? generated)
|
|
21
|
-
const helpId = computed(() => `${id.value}-help`)
|
|
22
|
-
const errorId = computed(() => `${id.value}-error`)
|
|
23
|
-
const describedBy = computed(() => {
|
|
24
|
-
const ids = [props.description && helpId.value, props.error && errorId.value].filter(Boolean)
|
|
25
|
-
return ids.length ? ids.join(' ') : undefined
|
|
26
|
-
})
|
|
27
|
-
</script>
|
|
28
|
-
|
|
29
|
-
<template>
|
|
30
|
-
<div :class="['oc-field', horizontal && 'oc-field--horizontal']">
|
|
31
|
-
<label v-if="label || $slots.label" class="oc-field-label" :for="id"><slot name="label">{{ label }}</slot></label>
|
|
32
|
-
<slot :id="id" :described-by="describedBy" :invalid="!!error" />
|
|
33
|
-
<p v-if="description" :id="helpId" class="oc-field-description">{{ description }}</p>
|
|
34
|
-
<p v-if="error" :id="errorId" class="oc-field-error" role="alert">{{ error }}</p>
|
|
35
|
-
</div>
|
|
36
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Form field: label, control (default slot), description and error.
|
|
4
|
+
* The slot receives { id, describedBy, invalid } so the control can be wired:
|
|
5
|
+
* <OcFormField label="Email" description="..." v-slot="f"><OcInput :id="f.id" :aria-describedby="f.describedBy" /></OcFormField>
|
|
6
|
+
* Classes: .oc-field(--horizontal), .oc-field-label, .oc-field-description, .oc-field-error
|
|
7
|
+
*/
|
|
8
|
+
import { computed } from 'vue'
|
|
9
|
+
import { useUid } from '../composables/useUid'
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
label?: string
|
|
13
|
+
description?: string
|
|
14
|
+
error?: string
|
|
15
|
+
/** id of the control; generated when omitted */
|
|
16
|
+
for?: string
|
|
17
|
+
horizontal?: boolean
|
|
18
|
+
}>()
|
|
19
|
+
const generated = useUid('oc-field')
|
|
20
|
+
const id = computed(() => props.for ?? generated)
|
|
21
|
+
const helpId = computed(() => `${id.value}-help`)
|
|
22
|
+
const errorId = computed(() => `${id.value}-error`)
|
|
23
|
+
const describedBy = computed(() => {
|
|
24
|
+
const ids = [props.description && helpId.value, props.error && errorId.value].filter(Boolean)
|
|
25
|
+
return ids.length ? ids.join(' ') : undefined
|
|
26
|
+
})
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<div :class="['oc-field', horizontal && 'oc-field--horizontal']">
|
|
31
|
+
<label v-if="label || $slots.label" class="oc-field-label" :for="id"><slot name="label">{{ label }}</slot></label>
|
|
32
|
+
<slot :id="id" :described-by="describedBy" :invalid="!!error" />
|
|
33
|
+
<p v-if="description" :id="helpId" class="oc-field-description">{{ description }}</p>
|
|
34
|
+
<p v-if="error" :id="errorId" class="oc-field-error" role="alert">{{ error }}</p>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcGreeting.vue | Map: time-of-day logic -> header -> icon slot -> body (eyebrow, title + name, date) -> actions slot -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Dashboard greeting. The time of day comes from `date` (default now): morning 05-12, afternoon 12-18, evening otherwise.
|
|
5
|
-
* Slots: icon, actions. Classes: .oc-greeting(--morning/--afternoon/--evening/--lg), -icon, -body, -eyebrow, -title, -name, -date, -actions
|
|
6
|
-
*/
|
|
7
|
-
import { computed } from 'vue'
|
|
8
|
-
|
|
9
|
-
type TimeOfDay = 'morning' | 'afternoon' | 'evening'
|
|
10
|
-
|
|
11
|
-
const props = defineProps<{
|
|
12
|
-
name?: string
|
|
13
|
-
date?: Date
|
|
14
|
-
timeOfDay?: TimeOfDay
|
|
15
|
-
/** Translated greetings, e.g. { morning: 'Goedemorgen' }. */
|
|
16
|
-
words?: Partial<Record<TimeOfDay, string>>
|
|
17
|
-
locale?: string
|
|
18
|
-
hideDate?: boolean
|
|
19
|
-
eyebrow?: string
|
|
20
|
-
size?: 'lg'
|
|
21
|
-
}>()
|
|
22
|
-
|
|
23
|
-
const WORDS: Record<TimeOfDay, string> = { morning: 'Good morning', afternoon: 'Good afternoon', evening: 'Good evening' }
|
|
24
|
-
|
|
25
|
-
const when = computed(() => props.date ?? new Date())
|
|
26
|
-
const tod = computed<TimeOfDay>(() => {
|
|
27
|
-
if (props.timeOfDay) return props.timeOfDay
|
|
28
|
-
const h = when.value.getHours()
|
|
29
|
-
return h >= 5 && h < 12 ? 'morning' : h >= 12 && h < 18 ? 'afternoon' : 'evening'
|
|
30
|
-
})
|
|
31
|
-
const word = computed(() => props.words?.[tod.value] ?? WORDS[tod.value])
|
|
32
|
-
const dateText = computed(() =>
|
|
33
|
-
new Intl.DateTimeFormat(props.locale, { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }).format(when.value),
|
|
34
|
-
)
|
|
35
|
-
// Local YYYY-MM-DD for datetime (toISOString would use UTC).
|
|
36
|
-
const iso = computed(() => {
|
|
37
|
-
const d = when.value
|
|
38
|
-
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
|
39
|
-
})
|
|
40
|
-
</script>
|
|
41
|
-
|
|
42
|
-
<template>
|
|
43
|
-
<header :class="['oc-greeting', `oc-greeting--${tod}`, size && `oc-greeting--${size}`]">
|
|
44
|
-
<span v-if="$slots.icon" class="oc-greeting-icon" aria-hidden="true"><slot name="icon" /></span>
|
|
45
|
-
<div class="oc-greeting-body">
|
|
46
|
-
<p v-if="eyebrow" class="oc-greeting-eyebrow">{{ eyebrow }}</p>
|
|
47
|
-
<h2 class="oc-greeting-title">{{ word }}<template v-if="name">, <span class="oc-greeting-name">{{ name }}</span></template></h2>
|
|
48
|
-
<time v-if="!hideDate" class="oc-greeting-date" :datetime="iso">{{ dateText }}</time>
|
|
49
|
-
</div>
|
|
50
|
-
<div v-if="$slots.actions" class="oc-greeting-actions"><slot name="actions" /></div>
|
|
51
|
-
</header>
|
|
52
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcGreeting.vue | Map: time-of-day logic -> header -> icon slot -> body (eyebrow, title + name, date) -> actions slot -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Dashboard greeting. The time of day comes from `date` (default now): morning 05-12, afternoon 12-18, evening otherwise.
|
|
5
|
+
* Slots: icon, actions. Classes: .oc-greeting(--morning/--afternoon/--evening/--lg), -icon, -body, -eyebrow, -title, -name, -date, -actions
|
|
6
|
+
*/
|
|
7
|
+
import { computed } from 'vue'
|
|
8
|
+
|
|
9
|
+
type TimeOfDay = 'morning' | 'afternoon' | 'evening'
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
name?: string
|
|
13
|
+
date?: Date
|
|
14
|
+
timeOfDay?: TimeOfDay
|
|
15
|
+
/** Translated greetings, e.g. { morning: 'Goedemorgen' }. */
|
|
16
|
+
words?: Partial<Record<TimeOfDay, string>>
|
|
17
|
+
locale?: string
|
|
18
|
+
hideDate?: boolean
|
|
19
|
+
eyebrow?: string
|
|
20
|
+
size?: 'lg'
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const WORDS: Record<TimeOfDay, string> = { morning: 'Good morning', afternoon: 'Good afternoon', evening: 'Good evening' }
|
|
24
|
+
|
|
25
|
+
const when = computed(() => props.date ?? new Date())
|
|
26
|
+
const tod = computed<TimeOfDay>(() => {
|
|
27
|
+
if (props.timeOfDay) return props.timeOfDay
|
|
28
|
+
const h = when.value.getHours()
|
|
29
|
+
return h >= 5 && h < 12 ? 'morning' : h >= 12 && h < 18 ? 'afternoon' : 'evening'
|
|
30
|
+
})
|
|
31
|
+
const word = computed(() => props.words?.[tod.value] ?? WORDS[tod.value])
|
|
32
|
+
const dateText = computed(() =>
|
|
33
|
+
new Intl.DateTimeFormat(props.locale, { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }).format(when.value),
|
|
34
|
+
)
|
|
35
|
+
// Local YYYY-MM-DD for datetime (toISOString would use UTC).
|
|
36
|
+
const iso = computed(() => {
|
|
37
|
+
const d = when.value
|
|
38
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
|
39
|
+
})
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<header :class="['oc-greeting', `oc-greeting--${tod}`, size && `oc-greeting--${size}`]">
|
|
44
|
+
<span v-if="$slots.icon" class="oc-greeting-icon" aria-hidden="true"><slot name="icon" /></span>
|
|
45
|
+
<div class="oc-greeting-body">
|
|
46
|
+
<p v-if="eyebrow" class="oc-greeting-eyebrow">{{ eyebrow }}</p>
|
|
47
|
+
<h2 class="oc-greeting-title">{{ word }}<template v-if="name">, <span class="oc-greeting-name">{{ name }}</span></template></h2>
|
|
48
|
+
<time v-if="!hideDate" class="oc-greeting-date" :datetime="iso">{{ dateText }}</time>
|
|
49
|
+
</div>
|
|
50
|
+
<div v-if="$slots.actions" class="oc-greeting-actions"><slot name="actions" /></div>
|
|
51
|
+
</header>
|
|
52
|
+
</template>
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Page or section title with a muted description. Classes: .oc-heading(--sm). `as` picks the title tag. */
|
|
3
|
-
withDefaults(
|
|
4
|
-
defineProps<{
|
|
5
|
-
title?: string
|
|
6
|
-
description?: string
|
|
7
|
-
size?: 'sm' | 'md'
|
|
8
|
-
as?: 'h1' | 'h2' | 'h3' | 'h4'
|
|
9
|
-
}>(),
|
|
10
|
-
{ size: 'md', as: 'h2' },
|
|
11
|
-
)
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<template>
|
|
15
|
-
<div :class="['oc-heading', size === 'sm' && 'oc-heading--sm']">
|
|
16
|
-
<component :is="as" class="oc-heading-title"><slot name="title">{{ title }}</slot></component>
|
|
17
|
-
<p v-if="description || $slots.default" class="oc-heading-description"><slot>{{ description }}</slot></p>
|
|
18
|
-
</div>
|
|
19
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Page or section title with a muted description. Classes: .oc-heading(--sm). `as` picks the title tag. */
|
|
3
|
+
withDefaults(
|
|
4
|
+
defineProps<{
|
|
5
|
+
title?: string
|
|
6
|
+
description?: string
|
|
7
|
+
size?: 'sm' | 'md'
|
|
8
|
+
as?: 'h1' | 'h2' | 'h3' | 'h4'
|
|
9
|
+
}>(),
|
|
10
|
+
{ size: 'md', as: 'h2' },
|
|
11
|
+
)
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div :class="['oc-heading', size === 'sm' && 'oc-heading--sm']">
|
|
16
|
+
<component :is="as" class="oc-heading-title"><slot name="title">{{ title }}</slot></component>
|
|
17
|
+
<p v-if="description || $slots.default" class="oc-heading-description"><slot>{{ description }}</slot></p>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Preview on hover/focus (interestfor + popover, CSS hover fallback). The trigger is the `trigger` slot
|
|
4
|
-
* (scoped: { id }) or a link with `label` and `href`; default slot = card content.
|
|
5
|
-
* Classes: .oc-hover-card(--top), -trigger, -content(--sm). Attributes land on the content element.
|
|
6
|
-
*/
|
|
7
|
-
import { useAttrs } from 'vue'
|
|
8
|
-
import { useUid } from '../composables/useUid'
|
|
9
|
-
|
|
10
|
-
defineOptions({ inheritAttrs: false })
|
|
11
|
-
defineProps<{ label?: string; href?: string; top?: boolean; small?: boolean }>()
|
|
12
|
-
const attrs = useAttrs()
|
|
13
|
-
const id = (attrs.id as string | undefined) ?? useUid('oc-hover-card')
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<template>
|
|
17
|
-
<span :class="['oc-hover-card', top && 'oc-hover-card--top']">
|
|
18
|
-
<slot name="trigger" :id="id">
|
|
19
|
-
<a class="oc-hover-card-trigger" :href="href ?? '#'" :interestfor="id">{{ label }}</a>
|
|
20
|
-
</slot>
|
|
21
|
-
<div v-bind="attrs" :id="id" :class="['oc-hover-card-content', small && 'oc-hover-card-content--sm']" popover>
|
|
22
|
-
<slot />
|
|
23
|
-
</div>
|
|
24
|
-
</span>
|
|
25
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Preview on hover/focus (interestfor + popover, CSS hover fallback). The trigger is the `trigger` slot
|
|
4
|
+
* (scoped: { id }) or a link with `label` and `href`; default slot = card content.
|
|
5
|
+
* Classes: .oc-hover-card(--top), -trigger, -content(--sm). Attributes land on the content element.
|
|
6
|
+
*/
|
|
7
|
+
import { useAttrs } from 'vue'
|
|
8
|
+
import { useUid } from '../composables/useUid'
|
|
9
|
+
|
|
10
|
+
defineOptions({ inheritAttrs: false })
|
|
11
|
+
defineProps<{ label?: string; href?: string; top?: boolean; small?: boolean }>()
|
|
12
|
+
const attrs = useAttrs()
|
|
13
|
+
const id = (attrs.id as string | undefined) ?? useUid('oc-hover-card')
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<span :class="['oc-hover-card', top && 'oc-hover-card--top']">
|
|
18
|
+
<slot name="trigger" :id="id">
|
|
19
|
+
<a class="oc-hover-card-trigger" :href="href ?? '#'" :interestfor="id">{{ label }}</a>
|
|
20
|
+
</slot>
|
|
21
|
+
<div v-bind="attrs" :id="id" :class="['oc-hover-card-content', small && 'oc-hover-card-content--sm']" popover>
|
|
22
|
+
<slot />
|
|
23
|
+
</div>
|
|
24
|
+
</span>
|
|
25
|
+
</template>
|