@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.
Files changed (178) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +49 -49
  3. package/dist/omniaui-vue.js.map +1 -1
  4. package/package.json +69 -69
  5. package/src/blocks/OcBlockAboutTimeline.vue +44 -44
  6. package/src/blocks/OcBlockAppHeader.vue +36 -36
  7. package/src/blocks/OcBlockAppShell.vue +29 -29
  8. package/src/blocks/OcBlockAuth.vue +24 -24
  9. package/src/blocks/OcBlockAuthSplit.vue +47 -47
  10. package/src/blocks/OcBlockContactGrid.vue +53 -53
  11. package/src/blocks/OcBlockCta.vue +12 -12
  12. package/src/blocks/OcBlockDataTable.vue +27 -27
  13. package/src/blocks/OcBlockFaq.vue +49 -49
  14. package/src/blocks/OcBlockFeatures.vue +23 -23
  15. package/src/blocks/OcBlockFooter.vue +25 -25
  16. package/src/blocks/OcBlockFormPage.vue +24 -24
  17. package/src/blocks/OcBlockHero.vue +13 -13
  18. package/src/blocks/OcBlockHeroCode.vue +42 -42
  19. package/src/blocks/OcBlockHeroOrbit.vue +69 -69
  20. package/src/blocks/OcBlockHeroSplit.vue +15 -15
  21. package/src/blocks/OcBlockModuleHeader.vue +21 -21
  22. package/src/blocks/OcBlockPageHero.vue +13 -13
  23. package/src/blocks/OcBlockPortfolioGrid.vue +63 -63
  24. package/src/blocks/OcBlockPricing.vue +39 -39
  25. package/src/blocks/OcBlockProcess.vue +39 -39
  26. package/src/blocks/OcBlockProductGrid.vue +41 -41
  27. package/src/blocks/OcBlockProfileHeader.vue +50 -50
  28. package/src/blocks/OcBlockShowcase.vue +43 -43
  29. package/src/blocks/OcBlockSiteFooter.vue +54 -54
  30. package/src/blocks/OcBlockSiteHeader.vue +34 -34
  31. package/src/blocks/OcBlockSpotlight.vue +47 -47
  32. package/src/blocks/OcBlockStats.vue +17 -17
  33. package/src/blocks/OcBlockStatsCounter.vue +20 -20
  34. package/src/blocks/OcBlockTeamList.vue +53 -53
  35. package/src/blocks/OcBlockTwoFactor.vue +61 -61
  36. package/src/blocks/OcBlockWizardDialog.vue +67 -67
  37. package/src/components/OcAccordion.vue +12 -12
  38. package/src/components/OcAccordionAccent.vue +27 -27
  39. package/src/components/OcAccordionItem.vue +19 -19
  40. package/src/components/OcAffixInput.vue +40 -40
  41. package/src/components/OcAlert.vue +17 -17
  42. package/src/components/OcAlertDialog.vue +66 -66
  43. package/src/components/OcAlphaTabs.vue +54 -54
  44. package/src/components/OcAspectRatio.vue +21 -21
  45. package/src/components/OcAttachment.vue +59 -59
  46. package/src/components/OcAttachmentImage.vue +13 -13
  47. package/src/components/OcAttachmentList.vue +31 -31
  48. package/src/components/OcAttachmentThumb.vue +14 -14
  49. package/src/components/OcAvatar.vue +18 -18
  50. package/src/components/OcAvatarGroup.vue +7 -7
  51. package/src/components/OcBackLink.vue +8 -8
  52. package/src/components/OcBadge.vue +13 -13
  53. package/src/components/OcBarcodeInput.vue +51 -51
  54. package/src/components/OcBreadcrumb.vue +22 -22
  55. package/src/components/OcBrowserFrame.vue +33 -33
  56. package/src/components/OcBusyOverlay.vue +32 -32
  57. package/src/components/OcButton.vue +47 -47
  58. package/src/components/OcButtonGroup.vue +10 -10
  59. package/src/components/OcButtonGroupText.vue +7 -7
  60. package/src/components/OcCard.vue +20 -20
  61. package/src/components/OcChartLine.vue +80 -80
  62. package/src/components/OcChatComposer.vue +42 -42
  63. package/src/components/OcCheckbox.vue +26 -26
  64. package/src/components/OcChecklist.vue +29 -29
  65. package/src/components/OcCollapsible.vue +19 -19
  66. package/src/components/OcColorField.vue +15 -15
  67. package/src/components/OcColorSelect.vue +33 -33
  68. package/src/components/OcColumnMenu.vue +50 -50
  69. package/src/components/OcCommand.vue +119 -119
  70. package/src/components/OcConfirmDialog.vue +65 -65
  71. package/src/components/OcCookieConsent.vue +119 -119
  72. package/src/components/OcCopyField.vue +37 -37
  73. package/src/components/OcCounter.vue +15 -15
  74. package/src/components/OcDataTable.vue +144 -144
  75. package/src/components/OcDefinitionList.vue +35 -35
  76. package/src/components/OcDropdown.vue +23 -23
  77. package/src/components/OcDropdownItem.vue +9 -9
  78. package/src/components/OcDropdownLabel.vue +7 -7
  79. package/src/components/OcDropdownSeparator.vue +7 -7
  80. package/src/components/OcDropzone.vue +36 -36
  81. package/src/components/OcEmojiPicker.vue +45 -45
  82. package/src/components/OcEmptyState.vue +13 -13
  83. package/src/components/OcEntityCard.vue +47 -47
  84. package/src/components/OcFactList.vue +37 -37
  85. package/src/components/OcFilterCounts.vue +38 -38
  86. package/src/components/OcFlipCard.vue +36 -36
  87. package/src/components/OcFormField.vue +36 -36
  88. package/src/components/OcGreeting.vue +52 -52
  89. package/src/components/OcHeading.vue +19 -19
  90. package/src/components/OcHoverCard.vue +25 -25
  91. package/src/components/OcImageZoom.vue +39 -39
  92. package/src/components/OcInput.vue +9 -9
  93. package/src/components/OcInputGroup.vue +12 -12
  94. package/src/components/OcItem.vue +40 -40
  95. package/src/components/OcItemGroup.vue +7 -7
  96. package/src/components/OcItemSeparator.vue +7 -7
  97. package/src/components/OcKbd.vue +7 -7
  98. package/src/components/OcKbdGroup.vue +15 -15
  99. package/src/components/OcLabel.vue +10 -10
  100. package/src/components/OcLanguageSelect.vue +40 -40
  101. package/src/components/OcLauncherCard.vue +34 -34
  102. package/src/components/OcLightbox.vue +63 -63
  103. package/src/components/OcLinkArrow.vue +25 -25
  104. package/src/components/OcListToolbar.vue +44 -44
  105. package/src/components/OcMarquee.vue +36 -36
  106. package/src/components/OcMediaCard.vue +28 -28
  107. package/src/components/OcMediaRow.vue +28 -28
  108. package/src/components/OcMediaThumb.vue +38 -38
  109. package/src/components/OcMessage.vue +91 -91
  110. package/src/components/OcMessageList.vue +43 -43
  111. package/src/components/OcMessageThread.vue +155 -155
  112. package/src/components/OcModal.vue +42 -42
  113. package/src/components/OcNavmenu.vue +18 -18
  114. package/src/components/OcNavmenuList.vue +38 -38
  115. package/src/components/OcNextTeaser.vue +25 -25
  116. package/src/components/OcNumberInput.vue +47 -47
  117. package/src/components/OcOtpInput.vue +34 -34
  118. package/src/components/OcPageHeader.vue +34 -34
  119. package/src/components/OcPagination.vue +33 -33
  120. package/src/components/OcPasswordInput.vue +27 -27
  121. package/src/components/OcPasswordRequirements.vue +28 -28
  122. package/src/components/OcPhoneInput.vue +72 -72
  123. package/src/components/OcPopover.vue +25 -25
  124. package/src/components/OcPriceCard.vue +69 -69
  125. package/src/components/OcProductMark.vue +31 -31
  126. package/src/components/OcProgress.vue +21 -21
  127. package/src/components/OcProjectCard.vue +36 -36
  128. package/src/components/OcProse.vue +57 -57
  129. package/src/components/OcRadio.vue +40 -40
  130. package/src/components/OcRadioGroup.vue +32 -32
  131. package/src/components/OcRecoveryCodes.vue +55 -55
  132. package/src/components/OcScrollArea.vue +15 -15
  133. package/src/components/OcSegmented.vue +28 -28
  134. package/src/components/OcSelect.vue +21 -21
  135. package/src/components/OcSeparator.vue +18 -18
  136. package/src/components/OcSeverityCard.vue +46 -46
  137. package/src/components/OcSheet.vue +49 -49
  138. package/src/components/OcSidebarCollapsible.vue +19 -19
  139. package/src/components/OcSidebarGroup.vue +11 -11
  140. package/src/components/OcSidebarMenu.vue +7 -7
  141. package/src/components/OcSidebarMenuButton.vue +31 -31
  142. package/src/components/OcSidebarSeparator.vue +7 -7
  143. package/src/components/OcSkeleton.vue +8 -8
  144. package/src/components/OcSlider.vue +27 -27
  145. package/src/components/OcSortableRow.vue +40 -40
  146. package/src/components/OcSpinner.vue +13 -13
  147. package/src/components/OcStackCard.vue +24 -24
  148. package/src/components/OcStackList.vue +31 -31
  149. package/src/components/OcStatCard.vue +39 -39
  150. package/src/components/OcStepper.vue +34 -34
  151. package/src/components/OcSwitch.vue +15 -15
  152. package/src/components/OcTable.vue +115 -115
  153. package/src/components/OcTableActions.vue +26 -26
  154. package/src/components/OcTableCompact.vue +46 -46
  155. package/src/components/OcTabs.vue +37 -37
  156. package/src/components/OcTagInput.vue +50 -50
  157. package/src/components/OcTerminalLog.vue +50 -50
  158. package/src/components/OcTextarea.vue +9 -9
  159. package/src/components/OcThemeSwitch.vue +31 -31
  160. package/src/components/OcTimeInput.vue +50 -50
  161. package/src/components/OcTimeline.vue +36 -36
  162. package/src/components/OcToast.vue +31 -31
  163. package/src/components/OcToastContainer.vue +21 -21
  164. package/src/components/OcToggle.vue +63 -63
  165. package/src/components/OcToggleField.vue +28 -28
  166. package/src/components/OcToggleGroup.vue +40 -40
  167. package/src/components/OcTooltip.vue +8 -8
  168. package/src/components/OcTopProgress.vue +54 -54
  169. package/src/components/OcTree.vue +70 -70
  170. package/src/components/OcUpgradeBanner.vue +42 -42
  171. package/src/components/OcUser.vue +25 -25
  172. package/src/components/OcVersionBanner.vue +34 -34
  173. package/src/composables/chartLine.ts +28 -28
  174. package/src/composables/keys.ts +22 -22
  175. package/src/composables/useAttrsSplit.ts +16 -16
  176. package/src/composables/useDialog.ts +21 -21
  177. package/src/composables/useToast.ts +66 -66
  178. package/src/composables/useUid.ts +12 -12
@@ -1,25 +1,25 @@
1
- <!-- File: packages/vue/src/components/OcLinkArrow.vue | Map: props (direction, tone, size) -> a.oc-link-arrow -->
2
- <script setup lang="ts">
3
- /** Text link with a moving arrow. Classes: .oc-link-arrow + --back/--external, --accent/--muted, --sm/--lg */
4
- withDefaults(
5
- defineProps<{
6
- href: string
7
- direction?: 'forward' | 'back' | 'external'
8
- tone?: 'default' | 'accent' | 'muted'
9
- size?: 'sm' | 'default' | 'lg'
10
- }>(),
11
- { direction: 'forward', tone: 'default', size: 'default' },
12
- )
13
- </script>
14
-
15
- <template>
16
- <a
17
- :class="[
18
- 'oc-link-arrow',
19
- direction !== 'forward' && `oc-link-arrow--${direction}`,
20
- tone !== 'default' && `oc-link-arrow--${tone}`,
21
- size !== 'default' && `oc-link-arrow--${size}`,
22
- ]"
23
- :href="href"
24
- ><slot /></a>
25
- </template>
1
+ <!-- File: packages/vue/src/components/OcLinkArrow.vue | Map: props (direction, tone, size) -> a.oc-link-arrow -->
2
+ <script setup lang="ts">
3
+ /** Text link with a moving arrow. Classes: .oc-link-arrow + --back/--external, --accent/--muted, --sm/--lg */
4
+ withDefaults(
5
+ defineProps<{
6
+ href: string
7
+ direction?: 'forward' | 'back' | 'external'
8
+ tone?: 'default' | 'accent' | 'muted'
9
+ size?: 'sm' | 'default' | 'lg'
10
+ }>(),
11
+ { direction: 'forward', tone: 'default', size: 'default' },
12
+ )
13
+ </script>
14
+
15
+ <template>
16
+ <a
17
+ :class="[
18
+ 'oc-link-arrow',
19
+ direction !== 'forward' && `oc-link-arrow--${direction}`,
20
+ tone !== 'default' && `oc-link-arrow--${tone}`,
21
+ size !== 'default' && `oc-link-arrow--${size}`,
22
+ ]"
23
+ :href="href"
24
+ ><slot /></a>
25
+ </template>
@@ -1,44 +1,44 @@
1
- <!-- File: packages/vue/src/components/OcListToolbar.vue | Map: toolbar row -> search slot -> count -> spacer -> view radios (built-in icons) -> actions slot -->
2
- <script setup lang="ts">
3
- /**
4
- * Toolbar above a list. v-model:view picks the view ("cards", "table" or "list"); render the matching view yourself.
5
- * Slots: search, actions, view-icon (scoped { view } to replace the built-in icons).
6
- * Classes: .oc-list-toolbar(--sticky/--bordered), -search, -count, -spacer, -views, -view, -actions
7
- */
8
- import { useUid } from '../composables/useUid'
9
-
10
- withDefaults(
11
- defineProps<{
12
- count?: string
13
- views?: Array<{ value: string; label: string }>
14
- viewsLabel?: string
15
- sticky?: boolean
16
- bordered?: boolean
17
- }>(),
18
- { views: () => [], viewsLabel: 'View' },
19
- )
20
- const view = defineModel<string>('view', { default: 'cards' })
21
- const name = useUid('oc-list-view')
22
- </script>
23
-
24
- <template>
25
- <div :class="['oc-list-toolbar', sticky && 'oc-list-toolbar--sticky', bordered && 'oc-list-toolbar--bordered']" role="toolbar">
26
- <div v-if="$slots.search" class="oc-list-toolbar-search"><slot name="search" /></div>
27
- <span v-if="count" class="oc-list-toolbar-count">{{ count }}</span>
28
- <span class="oc-list-toolbar-spacer"></span>
29
- <div v-if="views.length" class="oc-list-toolbar-views" role="radiogroup" :aria-label="viewsLabel">
30
- <label v-for="v in views" :key="v.value" class="oc-list-toolbar-view" :title="v.label">
31
- <input v-model="view" type="radio" :name="name" :value="v.value" :aria-label="v.label" />
32
- <slot name="view-icon" :view="v">
33
- <!-- Built-in icons for the three common views. -->
34
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
35
- <template v-if="v.value === 'cards'"><rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><rect x="14" y="14" width="7" height="7" rx="1" /></template>
36
- <path v-else-if="v.value === 'table'" d="M3 5h18M3 12h18M3 19h18" />
37
- <path v-else d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" />
38
- </svg>
39
- </slot>
40
- </label>
41
- </div>
42
- <div v-if="$slots.actions" class="oc-list-toolbar-actions"><slot name="actions" /></div>
43
- </div>
44
- </template>
1
+ <!-- File: packages/vue/src/components/OcListToolbar.vue | Map: toolbar row -> search slot -> count -> spacer -> view radios (built-in icons) -> actions slot -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Toolbar above a list. v-model:view picks the view ("cards", "table" or "list"); render the matching view yourself.
5
+ * Slots: search, actions, view-icon (scoped { view } to replace the built-in icons).
6
+ * Classes: .oc-list-toolbar(--sticky/--bordered), -search, -count, -spacer, -views, -view, -actions
7
+ */
8
+ import { useUid } from '../composables/useUid'
9
+
10
+ withDefaults(
11
+ defineProps<{
12
+ count?: string
13
+ views?: Array<{ value: string; label: string }>
14
+ viewsLabel?: string
15
+ sticky?: boolean
16
+ bordered?: boolean
17
+ }>(),
18
+ { views: () => [], viewsLabel: 'View' },
19
+ )
20
+ const view = defineModel<string>('view', { default: 'cards' })
21
+ const name = useUid('oc-list-view')
22
+ </script>
23
+
24
+ <template>
25
+ <div :class="['oc-list-toolbar', sticky && 'oc-list-toolbar--sticky', bordered && 'oc-list-toolbar--bordered']" role="toolbar">
26
+ <div v-if="$slots.search" class="oc-list-toolbar-search"><slot name="search" /></div>
27
+ <span v-if="count" class="oc-list-toolbar-count">{{ count }}</span>
28
+ <span class="oc-list-toolbar-spacer"></span>
29
+ <div v-if="views.length" class="oc-list-toolbar-views" role="radiogroup" :aria-label="viewsLabel">
30
+ <label v-for="v in views" :key="v.value" class="oc-list-toolbar-view" :title="v.label">
31
+ <input v-model="view" type="radio" :name="name" :value="v.value" :aria-label="v.label" />
32
+ <slot name="view-icon" :view="v">
33
+ <!-- Built-in icons for the three common views. -->
34
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
35
+ <template v-if="v.value === 'cards'"><rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><rect x="14" y="14" width="7" height="7" rx="1" /></template>
36
+ <path v-else-if="v.value === 'table'" d="M3 5h18M3 12h18M3 19h18" />
37
+ <path v-else d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" />
38
+ </svg>
39
+ </slot>
40
+ </label>
41
+ </div>
42
+ <div v-if="$slots.actions" class="oc-list-toolbar-actions"><slot name="actions" /></div>
43
+ </div>
44
+ </template>
@@ -1,36 +1,36 @@
1
- <!-- File: packages/vue/src/components/OcMarquee.vue | Map: props -> .oc-marquee -> track (items + aria-hidden copy) -->
2
- <script setup lang="ts">
3
- /**
4
- * Endless strip; pauses on hover / focus, stands still with reduced motion. The items are rendered twice
5
- * (the copy is aria-hidden). Classes: .oc-marquee (--reverse, --slow, --fast, --plain, --no-fade), __track, __item
6
- */
7
- withDefaults(
8
- defineProps<{
9
- items: string[]
10
- label?: string
11
- reverse?: boolean
12
- speed?: 'default' | 'slow' | 'fast'
13
- plain?: boolean
14
- noFade?: boolean
15
- }>(),
16
- { speed: 'default' },
17
- )
18
- </script>
19
-
20
- <template>
21
- <div
22
- :class="[
23
- 'oc-marquee',
24
- reverse && 'oc-marquee--reverse',
25
- speed !== 'default' && `oc-marquee--${speed}`,
26
- plain && 'oc-marquee--plain',
27
- noFade && 'oc-marquee--no-fade',
28
- ]"
29
- :aria-label="label"
30
- >
31
- <div class="oc-marquee__track">
32
- <span v-for="(item, i) in items" :key="i" class="oc-marquee__item">{{ item }}</span>
33
- <span v-for="(item, i) in items" :key="'copy-' + i" class="oc-marquee__item" aria-hidden="true">{{ item }}</span>
34
- </div>
35
- </div>
36
- </template>
1
+ <!-- File: packages/vue/src/components/OcMarquee.vue | Map: props -> .oc-marquee -> track (items + aria-hidden copy) -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Endless strip; pauses on hover / focus, stands still with reduced motion. The items are rendered twice
5
+ * (the copy is aria-hidden). Classes: .oc-marquee (--reverse, --slow, --fast, --plain, --no-fade), __track, __item
6
+ */
7
+ withDefaults(
8
+ defineProps<{
9
+ items: string[]
10
+ label?: string
11
+ reverse?: boolean
12
+ speed?: 'default' | 'slow' | 'fast'
13
+ plain?: boolean
14
+ noFade?: boolean
15
+ }>(),
16
+ { speed: 'default' },
17
+ )
18
+ </script>
19
+
20
+ <template>
21
+ <div
22
+ :class="[
23
+ 'oc-marquee',
24
+ reverse && 'oc-marquee--reverse',
25
+ speed !== 'default' && `oc-marquee--${speed}`,
26
+ plain && 'oc-marquee--plain',
27
+ noFade && 'oc-marquee--no-fade',
28
+ ]"
29
+ :aria-label="label"
30
+ >
31
+ <div class="oc-marquee__track">
32
+ <span v-for="(item, i) in items" :key="i" class="oc-marquee__item">{{ item }}</span>
33
+ <span v-for="(item, i) in items" :key="'copy-' + i" class="oc-marquee__item" aria-hidden="true">{{ item }}</span>
34
+ </div>
35
+ </div>
36
+ </template>
@@ -1,28 +1,28 @@
1
- <!-- File: packages/vue/src/components/OcMediaCard.vue | Map: props -> article -> OcMediaThumb -> body (avatar, title + meta, menu) -> popover menu -->
2
- <script setup lang="ts">
3
- /**
4
- * Video feed card (same markup as snippets/media-card.html). Props: title, href (title link covers the card), meta,
5
- * avatar (initials), thumb (props for OcMediaThumb), compact. Slots: avatar (replaces initials), meta, menu (dropdown items;
6
- * shows the ⋮ button). Classes: .oc-media-card(--compact), __body, __avatar, __text, __title, __meta, __menu
7
- */
8
- import OcMediaThumb from './OcMediaThumb.vue'
9
- import { useUid } from '../composables/useUid'
10
-
11
- defineProps<{ title: string; href: string; meta?: string; avatar?: string; thumb?: Record<string, unknown>; compact?: boolean }>()
12
- const menuId = useUid('oc-media-card-menu')
13
- </script>
14
-
15
- <template>
16
- <article :class="['oc-media-card', compact && 'oc-media-card--compact']">
17
- <OcMediaThumb v-bind="thumb" />
18
- <div class="oc-media-card__body">
19
- <span v-if="avatar || $slots.avatar" class="oc-media-card__avatar" aria-hidden="true"><slot name="avatar">{{ avatar }}</slot></span>
20
- <div class="oc-media-card__text">
21
- <h3 class="oc-media-card__title"><a :href="href">{{ title }}</a></h3>
22
- <p v-if="meta || $slots.meta" class="oc-media-card__meta"><slot name="meta">{{ meta }}</slot></p>
23
- </div>
24
- <button v-if="$slots.menu" type="button" class="oc-media-card__menu" :popovertarget="menuId" aria-label="More actions">&#8942;</button>
25
- </div>
26
- <div v-if="$slots.menu" :id="menuId" class="oc-dropdown-menu" popover><slot name="menu" /></div>
27
- </article>
28
- </template>
1
+ <!-- File: packages/vue/src/components/OcMediaCard.vue | Map: props -> article -> OcMediaThumb -> body (avatar, title + meta, menu) -> popover menu -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Video feed card (same markup as snippets/media-card.html). Props: title, href (title link covers the card), meta,
5
+ * avatar (initials), thumb (props for OcMediaThumb), compact. Slots: avatar (replaces initials), meta, menu (dropdown items;
6
+ * shows the ⋮ button). Classes: .oc-media-card(--compact), __body, __avatar, __text, __title, __meta, __menu
7
+ */
8
+ import OcMediaThumb from './OcMediaThumb.vue'
9
+ import { useUid } from '../composables/useUid'
10
+
11
+ defineProps<{ title: string; href: string; meta?: string; avatar?: string; thumb?: Record<string, unknown>; compact?: boolean }>()
12
+ const menuId = useUid('oc-media-card-menu')
13
+ </script>
14
+
15
+ <template>
16
+ <article :class="['oc-media-card', compact && 'oc-media-card--compact']">
17
+ <OcMediaThumb v-bind="thumb" />
18
+ <div class="oc-media-card__body">
19
+ <span v-if="avatar || $slots.avatar" class="oc-media-card__avatar" aria-hidden="true"><slot name="avatar">{{ avatar }}</slot></span>
20
+ <div class="oc-media-card__text">
21
+ <h3 class="oc-media-card__title"><a :href="href">{{ title }}</a></h3>
22
+ <p v-if="meta || $slots.meta" class="oc-media-card__meta"><slot name="meta">{{ meta }}</slot></p>
23
+ </div>
24
+ <button v-if="$slots.menu" type="button" class="oc-media-card__menu" :popovertarget="menuId" aria-label="More actions">&#8942;</button>
25
+ </div>
26
+ <div v-if="$slots.menu" :id="menuId" class="oc-dropdown-menu" popover><slot name="menu" /></div>
27
+ </article>
28
+ </template>
@@ -1,28 +1,28 @@
1
- <!-- File: packages/vue/src/components/OcMediaRow.vue | Map: props -> li row -> OcMediaThumb -> title link + meta -> menu button + popover -->
2
- <script setup lang="ts">
3
- /**
4
- * Compact media row (same markup as snippets/media-row.html). Put rows in
5
- * <ul class="oc-media-row-list"> or <ol class="oc-media-row-list oc-media-row-list--numbered"> (numbered playlist).
6
- * Props: title, href (link covers the row), meta, thumb (props for OcMediaThumb), current (aria-current).
7
- * Slots: meta, menu (dropdown items; shows the ⋮ button). Classes: .oc-media-row, __text, __title, __meta, __menu
8
- */
9
- import OcMediaThumb from './OcMediaThumb.vue'
10
- import { useUid } from '../composables/useUid'
11
-
12
- defineProps<{ title: string; href: string; meta?: string; thumb?: Record<string, unknown>; current?: boolean }>()
13
- const menuId = useUid('oc-media-row-menu')
14
- </script>
15
-
16
- <template>
17
- <li class="oc-media-row" :aria-current="current ? 'true' : undefined">
18
- <OcMediaThumb v-bind="thumb" />
19
- <div class="oc-media-row__text">
20
- <h3 class="oc-media-row__title"><a :href="href">{{ title }}</a></h3>
21
- <p v-if="meta || $slots.meta" class="oc-media-row__meta"><slot name="meta">{{ meta }}</slot></p>
22
- </div>
23
- <template v-if="$slots.menu">
24
- <button type="button" class="oc-media-row__menu" :popovertarget="menuId" aria-label="More actions">&#8942;</button>
25
- <div :id="menuId" class="oc-dropdown-menu" popover><slot name="menu" /></div>
26
- </template>
27
- </li>
28
- </template>
1
+ <!-- File: packages/vue/src/components/OcMediaRow.vue | Map: props -> li row -> OcMediaThumb -> title link + meta -> menu button + popover -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Compact media row (same markup as snippets/media-row.html). Put rows in
5
+ * <ul class="oc-media-row-list"> or <ol class="oc-media-row-list oc-media-row-list--numbered"> (numbered playlist).
6
+ * Props: title, href (link covers the row), meta, thumb (props for OcMediaThumb), current (aria-current).
7
+ * Slots: meta, menu (dropdown items; shows the ⋮ button). Classes: .oc-media-row, __text, __title, __meta, __menu
8
+ */
9
+ import OcMediaThumb from './OcMediaThumb.vue'
10
+ import { useUid } from '../composables/useUid'
11
+
12
+ defineProps<{ title: string; href: string; meta?: string; thumb?: Record<string, unknown>; current?: boolean }>()
13
+ const menuId = useUid('oc-media-row-menu')
14
+ </script>
15
+
16
+ <template>
17
+ <li class="oc-media-row" :aria-current="current ? 'true' : undefined">
18
+ <OcMediaThumb v-bind="thumb" />
19
+ <div class="oc-media-row__text">
20
+ <h3 class="oc-media-row__title"><a :href="href">{{ title }}</a></h3>
21
+ <p v-if="meta || $slots.meta" class="oc-media-row__meta"><slot name="meta">{{ meta }}</slot></p>
22
+ </div>
23
+ <template v-if="$slots.menu">
24
+ <button type="button" class="oc-media-row__menu" :popovertarget="menuId" aria-label="More actions">&#8942;</button>
25
+ <div :id="menuId" class="oc-dropdown-menu" popover><slot name="menu" /></div>
26
+ </template>
27
+ </li>
28
+ </template>
@@ -1,38 +1,38 @@
1
- <!-- File: packages/vue/src/components/OcMediaThumb.vue | Map: props -> <a>/<div> -> picture -> badge -> duration -> progress -->
2
- <script setup lang="ts">
3
- /**
4
- * 16:9 video thumbnail (same markup as snippets/media-thumb.html). href renders an <a>.
5
- * Props: src, alt, art (placeholder variant 1-6), duration, badge, live, progress (0-100), watched, ratio (video|square|portrait).
6
- * Default slot: extra overlay content. Classes: .oc-media-thumb(--square/--portrait, .is-watched), __badge(--live), __duration, __progress
7
- */
8
- withDefaults(
9
- defineProps<{
10
- src?: string
11
- alt?: string
12
- art?: number
13
- href?: string
14
- duration?: string
15
- badge?: string
16
- live?: boolean
17
- progress?: number
18
- watched?: boolean
19
- ratio?: 'video' | 'square' | 'portrait'
20
- }>(),
21
- { alt: '', ratio: 'video' },
22
- )
23
- </script>
24
-
25
- <template>
26
- <component
27
- :is="href ? 'a' : 'div'"
28
- :href="href"
29
- :class="['oc-media-thumb', ratio !== 'video' && `oc-media-thumb--${ratio}`, watched && 'is-watched']"
30
- >
31
- <img v-if="src" :src="src" :alt="alt" />
32
- <div v-else :class="['oc-media-art', art && art > 1 && `oc-media-art--${art}`]" />
33
- <span v-if="badge" :class="['oc-media-thumb__badge', live && 'oc-media-thumb__badge--live']">{{ badge }}</span>
34
- <span v-if="duration" class="oc-media-thumb__duration">{{ duration }}</span>
35
- <progress v-if="progress != null" class="oc-media-thumb__progress" :value="progress" max="100" :aria-label="`Watched ${progress}%`">{{ progress }}%</progress>
36
- <slot />
37
- </component>
38
- </template>
1
+ <!-- File: packages/vue/src/components/OcMediaThumb.vue | Map: props -> <a>/<div> -> picture -> badge -> duration -> progress -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * 16:9 video thumbnail (same markup as snippets/media-thumb.html). href renders an <a>.
5
+ * Props: src, alt, art (placeholder variant 1-6), duration, badge, live, progress (0-100), watched, ratio (video|square|portrait).
6
+ * Default slot: extra overlay content. Classes: .oc-media-thumb(--square/--portrait, .is-watched), __badge(--live), __duration, __progress
7
+ */
8
+ withDefaults(
9
+ defineProps<{
10
+ src?: string
11
+ alt?: string
12
+ art?: number
13
+ href?: string
14
+ duration?: string
15
+ badge?: string
16
+ live?: boolean
17
+ progress?: number
18
+ watched?: boolean
19
+ ratio?: 'video' | 'square' | 'portrait'
20
+ }>(),
21
+ { alt: '', ratio: 'video' },
22
+ )
23
+ </script>
24
+
25
+ <template>
26
+ <component
27
+ :is="href ? 'a' : 'div'"
28
+ :href="href"
29
+ :class="['oc-media-thumb', ratio !== 'video' && `oc-media-thumb--${ratio}`, watched && 'is-watched']"
30
+ >
31
+ <img v-if="src" :src="src" :alt="alt" />
32
+ <div v-else :class="['oc-media-art', art && art > 1 && `oc-media-art--${art}`]" />
33
+ <span v-if="badge" :class="['oc-media-thumb__badge', live && 'oc-media-thumb__badge--live']">{{ badge }}</span>
34
+ <span v-if="duration" class="oc-media-thumb__duration">{{ duration }}</span>
35
+ <progress v-if="progress != null" class="oc-media-thumb__progress" :value="progress" max="100" :aria-label="`Watched ${progress}%`">{{ progress }}%</progress>
36
+ <slot />
37
+ </component>
38
+ </template>
@@ -1,91 +1,91 @@
1
- <!-- File: packages/vue/src/components/OcMessage.vue | Map: types + icons -> props -> article (avatar, bubble: sender, quote, text, attachments, meta; actions) -->
2
- <script lang="ts">
3
- import type { AttachmentFile } from './OcAttachment.vue'
4
-
5
- /** File chip (default), image preview or square thumbnail inside a message. */
6
- export type MessageAttachment =
7
- | (AttachmentFile & { kind?: 'file' })
8
- | { kind: 'image'; href?: string; src?: string; alt: string; art?: number; caption?: string }
9
- | { kind: 'thumb'; src?: string; alt: string; art?: number }
10
- export interface MessageAction { name: string; label: string; icon?: 'reply' | 'delete'; destructive?: boolean }
11
- export interface MessageData {
12
- direction?: 'in' | 'out'
13
- sender?: string
14
- avatar?: string
15
- time?: string
16
- datetime?: string
17
- status?: 'sent' | 'read' | 'failed'
18
- statusLabel?: string
19
- statusIcon?: boolean
20
- continued?: boolean
21
- quote?: { href?: string; sender: string; text: string }
22
- /** Plain text: one paragraph. */
23
- text?: string
24
- /** Trusted markup (p, ul, ol, a, strong, code); escape user text on the server. */
25
- html?: string
26
- attachments?: MessageAttachment[]
27
- actions?: MessageAction[]
28
- label?: string
29
- id?: string
30
- }
31
- export const MESSAGE_ICONS: Record<string, string> = {
32
- reply: 'M9 14 4 9l5-5M4 9h10a6 6 0 0 1 6 6v4',
33
- delete: 'M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6M10 11v6M14 11v6',
34
- }
35
- </script>
36
-
37
- <script setup lang="ts">
38
- /**
39
- * Chat bubble (same markup as snippets/message.html): incoming / outgoing, sender, avatar, time, read receipt, reply quote,
40
- * attachments and hover actions. Slot default = extra text content. Emits: action(name).
41
- * Classes: .oc-message(--out/--continued) __avatar __bubble __sender __quote __text __attachments __meta __status(--read/--failed) __actions __action
42
- */
43
- import { computed } from 'vue'
44
- import OcAttachment from './OcAttachment.vue'
45
- import OcAttachmentImage from './OcAttachmentImage.vue'
46
- import OcAttachmentThumb from './OcAttachmentThumb.vue'
47
-
48
- const props = withDefaults(defineProps<MessageData>(), { direction: 'in' })
49
- const emit = defineEmits<{ action: [name: string] }>()
50
- const STATUS = { sent: 'Sent', read: 'Read', failed: 'Failed' }
51
- const thumbsOnly = computed(() => !!props.attachments?.length && props.attachments.every((a) => a.kind === 'thumb'))
52
- // Props of one attachment without its `kind`.
53
- type ImageAttachment = Extract<MessageAttachment, { kind: 'image' }>
54
- type ThumbAttachment = Extract<MessageAttachment, { kind: 'thumb' }>
55
- const strip = (a: MessageAttachment) => {
56
- const { kind: _k, ...p } = a
57
- return p
58
- }
59
- const image = (a: MessageAttachment) => strip(a) as Omit<ImageAttachment, 'kind'>
60
- const thumb = (a: MessageAttachment) => strip(a) as Omit<ThumbAttachment, 'kind'>
61
- const file = (a: MessageAttachment) => strip(a) as AttachmentFile
62
- </script>
63
-
64
- <template>
65
- <article :id="id" :class="['oc-message', direction === 'out' && 'oc-message--out', continued && 'oc-message--continued']" :aria-label="label">
66
- <span class="oc-avatar oc-message__avatar">{{ avatar }}</span>
67
- <div class="oc-message__bubble">
68
- <p v-if="sender && !continued" class="oc-message__sender">{{ sender }}</p>
69
- <a v-if="quote" class="oc-message__quote" :href="quote.href"><strong>{{ quote.sender }}</strong><span>{{ quote.text }}</span></a>
70
- <div v-if="html" class="oc-message__text" v-html="html"></div>
71
- <div v-else-if="text || $slots.default" class="oc-message__text"><p v-if="text">{{ text }}</p><slot /></div>
72
- <div v-if="attachments?.length" class="oc-message__attachments">
73
- <div v-if="thumbsOnly" class="oc-attachment-list"><OcAttachmentThumb v-for="(a, i) in attachments" :key="i" v-bind="thumb(a)" as="span" /></div>
74
- <template v-for="(a, i) in attachments" v-else :key="i">
75
- <OcAttachmentImage v-if="a.kind === 'image'" v-bind="image(a)" />
76
- <OcAttachmentThumb v-else-if="a.kind === 'thumb'" v-bind="thumb(a)" as="span" />
77
- <OcAttachment v-else v-bind="file(a)" />
78
- </template>
79
- </div>
80
- <p v-if="time || status" class="oc-message__meta">
81
- <time v-if="time" :datetime="datetime">{{ time }}</time>
82
- <span v-if="status" :class="['oc-message__status', status !== 'sent' && `oc-message__status--${status}`]"><svg v-if="statusIcon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12.5 7 17.5 18 6.5M13 16.5l1 1L22.5 9" /></svg>{{ statusLabel ?? STATUS[status] }}</span>
83
- </p>
84
- </div>
85
- <div v-if="actions?.length" class="oc-message__actions">
86
- <button v-for="a in actions" :key="a.name" type="button" :class="['oc-message__action', a.destructive && 'oc-message__action--destructive']" :aria-label="a.label" @click="emit('action', a.name)">
87
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path :d="MESSAGE_ICONS[a.icon ?? a.name] ?? MESSAGE_ICONS.reply" /></svg>
88
- </button>
89
- </div>
90
- </article>
91
- </template>
1
+ <!-- File: packages/vue/src/components/OcMessage.vue | Map: types + icons -> props -> article (avatar, bubble: sender, quote, text, attachments, meta; actions) -->
2
+ <script lang="ts">
3
+ import type { AttachmentFile } from './OcAttachment.vue'
4
+
5
+ /** File chip (default), image preview or square thumbnail inside a message. */
6
+ export type MessageAttachment =
7
+ | (AttachmentFile & { kind?: 'file' })
8
+ | { kind: 'image'; href?: string; src?: string; alt: string; art?: number; caption?: string }
9
+ | { kind: 'thumb'; src?: string; alt: string; art?: number }
10
+ export interface MessageAction { name: string; label: string; icon?: 'reply' | 'delete'; destructive?: boolean }
11
+ export interface MessageData {
12
+ direction?: 'in' | 'out'
13
+ sender?: string
14
+ avatar?: string
15
+ time?: string
16
+ datetime?: string
17
+ status?: 'sent' | 'read' | 'failed'
18
+ statusLabel?: string
19
+ statusIcon?: boolean
20
+ continued?: boolean
21
+ quote?: { href?: string; sender: string; text: string }
22
+ /** Plain text: one paragraph. */
23
+ text?: string
24
+ /** Trusted markup (p, ul, ol, a, strong, code); escape user text on the server. */
25
+ html?: string
26
+ attachments?: MessageAttachment[]
27
+ actions?: MessageAction[]
28
+ label?: string
29
+ id?: string
30
+ }
31
+ export const MESSAGE_ICONS: Record<string, string> = {
32
+ reply: 'M9 14 4 9l5-5M4 9h10a6 6 0 0 1 6 6v4',
33
+ delete: 'M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6M10 11v6M14 11v6',
34
+ }
35
+ </script>
36
+
37
+ <script setup lang="ts">
38
+ /**
39
+ * Chat bubble (same markup as snippets/message.html): incoming / outgoing, sender, avatar, time, read receipt, reply quote,
40
+ * attachments and hover actions. Slot default = extra text content. Emits: action(name).
41
+ * Classes: .oc-message(--out/--continued) __avatar __bubble __sender __quote __text __attachments __meta __status(--read/--failed) __actions __action
42
+ */
43
+ import { computed } from 'vue'
44
+ import OcAttachment from './OcAttachment.vue'
45
+ import OcAttachmentImage from './OcAttachmentImage.vue'
46
+ import OcAttachmentThumb from './OcAttachmentThumb.vue'
47
+
48
+ const props = withDefaults(defineProps<MessageData>(), { direction: 'in' })
49
+ const emit = defineEmits<{ action: [name: string] }>()
50
+ const STATUS = { sent: 'Sent', read: 'Read', failed: 'Failed' }
51
+ const thumbsOnly = computed(() => !!props.attachments?.length && props.attachments.every((a) => a.kind === 'thumb'))
52
+ // Props of one attachment without its `kind`.
53
+ type ImageAttachment = Extract<MessageAttachment, { kind: 'image' }>
54
+ type ThumbAttachment = Extract<MessageAttachment, { kind: 'thumb' }>
55
+ const strip = (a: MessageAttachment) => {
56
+ const { kind: _k, ...p } = a
57
+ return p
58
+ }
59
+ const image = (a: MessageAttachment) => strip(a) as Omit<ImageAttachment, 'kind'>
60
+ const thumb = (a: MessageAttachment) => strip(a) as Omit<ThumbAttachment, 'kind'>
61
+ const file = (a: MessageAttachment) => strip(a) as AttachmentFile
62
+ </script>
63
+
64
+ <template>
65
+ <article :id="id" :class="['oc-message', direction === 'out' && 'oc-message--out', continued && 'oc-message--continued']" :aria-label="label">
66
+ <span class="oc-avatar oc-message__avatar">{{ avatar }}</span>
67
+ <div class="oc-message__bubble">
68
+ <p v-if="sender && !continued" class="oc-message__sender">{{ sender }}</p>
69
+ <a v-if="quote" class="oc-message__quote" :href="quote.href"><strong>{{ quote.sender }}</strong><span>{{ quote.text }}</span></a>
70
+ <div v-if="html" class="oc-message__text" v-html="html"></div>
71
+ <div v-else-if="text || $slots.default" class="oc-message__text"><p v-if="text">{{ text }}</p><slot /></div>
72
+ <div v-if="attachments?.length" class="oc-message__attachments">
73
+ <div v-if="thumbsOnly" class="oc-attachment-list"><OcAttachmentThumb v-for="(a, i) in attachments" :key="i" v-bind="thumb(a)" as="span" /></div>
74
+ <template v-for="(a, i) in attachments" v-else :key="i">
75
+ <OcAttachmentImage v-if="a.kind === 'image'" v-bind="image(a)" />
76
+ <OcAttachmentThumb v-else-if="a.kind === 'thumb'" v-bind="thumb(a)" as="span" />
77
+ <OcAttachment v-else v-bind="file(a)" />
78
+ </template>
79
+ </div>
80
+ <p v-if="time || status" class="oc-message__meta">
81
+ <time v-if="time" :datetime="datetime">{{ time }}</time>
82
+ <span v-if="status" :class="['oc-message__status', status !== 'sent' && `oc-message__status--${status}`]"><svg v-if="statusIcon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12.5 7 17.5 18 6.5M13 16.5l1 1L22.5 9" /></svg>{{ statusLabel ?? STATUS[status] }}</span>
83
+ </p>
84
+ </div>
85
+ <div v-if="actions?.length" class="oc-message__actions">
86
+ <button v-for="a in actions" :key="a.name" type="button" :class="['oc-message__action', a.destructive && 'oc-message__action--destructive']" :aria-label="a.label" @click="emit('action', a.name)">
87
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path :d="MESSAGE_ICONS[a.icon ?? a.name] ?? MESSAGE_ICONS.reply" /></svg>
88
+ </button>
89
+ </div>
90
+ </article>
91
+ </template>