@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,32 +1,32 @@
1
- <!-- File: packages/vue/src/components/OcBusyOverlay.vue | Map: props + model -> <dialog closedby="none"> (spinner or progress, title, text) -->
2
- <script setup lang="ts">
3
- /**
4
- * Modal "please wait" overlay (same markup as snippets/busy-overlay.html). v-model:open shows it while a long submit or
5
- * upload runs; the page is inert and Esc does not close it. `progress` (0-100) swaps the spinner for a progress bar.
6
- * Slot: default (replaces the text). Classes: .oc-busy-overlay __title __text; .oc-spinner / .oc-progress
7
- */
8
- import { ref } from 'vue'
9
- import { useDialog } from '../composables/useDialog'
10
- import { useUid } from '../composables/useUid'
11
- import OcProgress from './OcProgress.vue'
12
-
13
- const props = withDefaults(defineProps<{ title?: string; text?: string; progress?: number; label?: string; id?: string }>(), {
14
- title: 'Saving…',
15
- text: 'The photos are being processed. This can take a minute; please keep this page open.',
16
- label: 'Busy',
17
- })
18
- const open = defineModel<boolean>('open', { default: false })
19
- const el = ref<HTMLDialogElement | null>(null)
20
- const { onClose } = useDialog(open, el)
21
- const auto = useUid('oc-busy-overlay')
22
- const base = props.id ?? auto
23
- </script>
24
-
25
- <template>
26
- <dialog :id="base" ref="el" class="oc-busy-overlay" closedby="none" :aria-labelledby="`${base}-title`" :aria-describedby="`${base}-text`" @close="onClose">
27
- <span v-if="progress == null" class="oc-spinner" role="status" :aria-label="label"></span>
28
- <OcProgress v-else :value="progress" :label="label" />
29
- <h2 :id="`${base}-title`" class="oc-busy-overlay__title">{{ title }}</h2>
30
- <p :id="`${base}-text`" class="oc-busy-overlay__text"><slot>{{ text }}</slot></p>
31
- </dialog>
32
- </template>
1
+ <!-- File: packages/vue/src/components/OcBusyOverlay.vue | Map: props + model -> <dialog closedby="none"> (spinner or progress, title, text) -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Modal "please wait" overlay (same markup as snippets/busy-overlay.html). v-model:open shows it while a long submit or
5
+ * upload runs; the page is inert and Esc does not close it. `progress` (0-100) swaps the spinner for a progress bar.
6
+ * Slot: default (replaces the text). Classes: .oc-busy-overlay __title __text; .oc-spinner / .oc-progress
7
+ */
8
+ import { ref } from 'vue'
9
+ import { useDialog } from '../composables/useDialog'
10
+ import { useUid } from '../composables/useUid'
11
+ import OcProgress from './OcProgress.vue'
12
+
13
+ const props = withDefaults(defineProps<{ title?: string; text?: string; progress?: number; label?: string; id?: string }>(), {
14
+ title: 'Saving…',
15
+ text: 'The photos are being processed. This can take a minute; please keep this page open.',
16
+ label: 'Busy',
17
+ })
18
+ const open = defineModel<boolean>('open', { default: false })
19
+ const el = ref<HTMLDialogElement | null>(null)
20
+ const { onClose } = useDialog(open, el)
21
+ const auto = useUid('oc-busy-overlay')
22
+ const base = props.id ?? auto
23
+ </script>
24
+
25
+ <template>
26
+ <dialog :id="base" ref="el" class="oc-busy-overlay" closedby="none" :aria-labelledby="`${base}-title`" :aria-describedby="`${base}-text`" @close="onClose">
27
+ <span v-if="progress == null" class="oc-spinner" role="status" :aria-label="label"></span>
28
+ <OcProgress v-else :value="progress" :label="label" />
29
+ <h2 :id="`${base}-title`" class="oc-busy-overlay__title">{{ title }}</h2>
30
+ <p :id="`${base}-text`" class="oc-busy-overlay__text"><slot>{{ text }}</slot></p>
31
+ </dialog>
32
+ </template>
@@ -1,47 +1,47 @@
1
- <script setup lang="ts">
2
- /**
3
- * Button. Renders <a> when `href` is set, otherwise <button>.
4
- * Classes: .oc-btn + --secondary/--outline/--ghost/--link/--destructive/--success/--gradient/--glass,
5
- * --xs/--sm/--lg, --icon, --pill
6
- */
7
- withDefaults(
8
- defineProps<{
9
- variant?: 'default' | 'secondary' | 'outline' | 'ghost' | 'link' | 'destructive' | 'success' | 'gradient' | 'glass'
10
- size?: 'xs' | 'sm' | 'md' | 'lg'
11
- icon?: boolean
12
- pill?: boolean
13
- href?: string
14
- type?: 'button' | 'submit' | 'reset'
15
- }>(),
16
- { variant: 'default', size: 'md', type: 'button' },
17
- )
18
- </script>
19
-
20
- <template>
21
- <a
22
- v-if="href"
23
- :href="href"
24
- :class="[
25
- 'oc-btn',
26
- variant !== 'default' && `oc-btn--${variant}`,
27
- size !== 'md' && `oc-btn--${size}`,
28
- icon && 'oc-btn--icon',
29
- pill && 'oc-btn--pill',
30
- ]"
31
- >
32
- <slot />
33
- </a>
34
- <button
35
- v-else
36
- :type="type"
37
- :class="[
38
- 'oc-btn',
39
- variant !== 'default' && `oc-btn--${variant}`,
40
- size !== 'md' && `oc-btn--${size}`,
41
- icon && 'oc-btn--icon',
42
- pill && 'oc-btn--pill',
43
- ]"
44
- >
45
- <slot />
46
- </button>
47
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Button. Renders <a> when `href` is set, otherwise <button>.
4
+ * Classes: .oc-btn + --secondary/--outline/--ghost/--link/--destructive/--success/--gradient/--glass,
5
+ * --xs/--sm/--lg, --icon, --pill
6
+ */
7
+ withDefaults(
8
+ defineProps<{
9
+ variant?: 'default' | 'secondary' | 'outline' | 'ghost' | 'link' | 'destructive' | 'success' | 'gradient' | 'glass'
10
+ size?: 'xs' | 'sm' | 'md' | 'lg'
11
+ icon?: boolean
12
+ pill?: boolean
13
+ href?: string
14
+ type?: 'button' | 'submit' | 'reset'
15
+ }>(),
16
+ { variant: 'default', size: 'md', type: 'button' },
17
+ )
18
+ </script>
19
+
20
+ <template>
21
+ <a
22
+ v-if="href"
23
+ :href="href"
24
+ :class="[
25
+ 'oc-btn',
26
+ variant !== 'default' && `oc-btn--${variant}`,
27
+ size !== 'md' && `oc-btn--${size}`,
28
+ icon && 'oc-btn--icon',
29
+ pill && 'oc-btn--pill',
30
+ ]"
31
+ >
32
+ <slot />
33
+ </a>
34
+ <button
35
+ v-else
36
+ :type="type"
37
+ :class="[
38
+ 'oc-btn',
39
+ variant !== 'default' && `oc-btn--${variant}`,
40
+ size !== 'md' && `oc-btn--${size}`,
41
+ icon && 'oc-btn--icon',
42
+ pill && 'oc-btn--pill',
43
+ ]"
44
+ >
45
+ <slot />
46
+ </button>
47
+ </template>
@@ -1,10 +1,10 @@
1
- <script setup lang="ts">
2
- /** Joined buttons with shared borders. `label` becomes aria-label. Put OcButton (usually variant outline) and OcButtonGroupText inside. Classes: .oc-btn-group(--vertical) */
3
- defineProps<{ vertical?: boolean; label?: string }>()
4
- </script>
5
-
6
- <template>
7
- <div :class="['oc-btn-group', vertical && 'oc-btn-group--vertical']" role="group" :aria-label="label">
8
- <slot />
9
- </div>
10
- </template>
1
+ <script setup lang="ts">
2
+ /** Joined buttons with shared borders. `label` becomes aria-label. Put OcButton (usually variant outline) and OcButtonGroupText inside. Classes: .oc-btn-group(--vertical) */
3
+ defineProps<{ vertical?: boolean; label?: string }>()
4
+ </script>
5
+
6
+ <template>
7
+ <div :class="['oc-btn-group', vertical && 'oc-btn-group--vertical']" role="group" :aria-label="label">
8
+ <slot />
9
+ </div>
10
+ </template>
@@ -1,7 +1,7 @@
1
- <script setup lang="ts">
2
- /** Static text cell inside OcButtonGroup (a count, a unit). Class: .oc-btn-group-text */
3
- </script>
4
-
5
- <template>
6
- <span class="oc-btn-group-text"><slot /></span>
7
- </template>
1
+ <script setup lang="ts">
2
+ /** Static text cell inside OcButtonGroup (a count, a unit). Class: .oc-btn-group-text */
3
+ </script>
4
+
5
+ <template>
6
+ <span class="oc-btn-group-text"><slot /></span>
7
+ </template>
@@ -1,20 +1,20 @@
1
- <script setup lang="ts">
2
- /**
3
- * Card with header, content and footer.
4
- * Slots: header (replaces title/description), default (content), footer. Classes: .oc-card--sm
5
- */
6
- withDefaults(defineProps<{ title?: string; description?: string; size?: 'sm' | 'md' }>(), { size: 'md' })
7
- </script>
8
-
9
- <template>
10
- <div :class="['oc-card', size === 'sm' && 'oc-card--sm']">
11
- <div v-if="title || description || $slots.header" class="oc-card-header">
12
- <slot name="header">
13
- <h3 v-if="title" class="oc-card-title">{{ title }}</h3>
14
- <p v-if="description" class="oc-card-description">{{ description }}</p>
15
- </slot>
16
- </div>
17
- <div v-if="$slots.default" class="oc-card-content"><slot /></div>
18
- <div v-if="$slots.footer" class="oc-card-footer"><slot name="footer" /></div>
19
- </div>
20
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Card with header, content and footer.
4
+ * Slots: header (replaces title/description), default (content), footer. Classes: .oc-card--sm
5
+ */
6
+ withDefaults(defineProps<{ title?: string; description?: string; size?: 'sm' | 'md' }>(), { size: 'md' })
7
+ </script>
8
+
9
+ <template>
10
+ <div :class="['oc-card', size === 'sm' && 'oc-card--sm']">
11
+ <div v-if="title || description || $slots.header" class="oc-card-header">
12
+ <slot name="header">
13
+ <h3 v-if="title" class="oc-card-title">{{ title }}</h3>
14
+ <p v-if="description" class="oc-card-description">{{ description }}</p>
15
+ </slot>
16
+ </div>
17
+ <div v-if="$slots.default" class="oc-card-content"><slot /></div>
18
+ <div v-if="$slots.footer" class="oc-card-footer"><slot name="footer" /></div>
19
+ </div>
20
+ </template>
@@ -1,80 +1,80 @@
1
- <!-- File: packages/vue/src/components/OcChartLine.vue | Map: props + demo data -> computed axis / paths -> figure (head, y axis, plot: svg + hover columns, x axis, legend) -->
2
- <script setup lang="ts">
3
- /**
4
- * Line / area chart in inline SVG (same markup as snippets/chart-line.html); tooltips open on :hover / :focus-visible (CSS only).
5
- * series: { name, values, dashed? }[]; labels = one per point (tooltips), xLabels = short axis labels. max defaults to a round
6
- * number above the data. variant: line | area | spark; dots = always visible. Classes: .oc-chart-line(--area/--dots/--spark) __head
7
- * __title __sub __value __body __y __plot __svg __series __area __path(--dashed) __cols __col __dot __tip __row __x; .oc-chart-legend
8
- */
9
- import { computed } from 'vue'
10
- import { chartLinePath, n2, niceMax } from '../composables/chartLine'
11
-
12
- export interface ChartLineSeries { name: string; values: number[]; dashed?: boolean }
13
- const props = withDefaults(
14
- defineProps<{
15
- title?: string
16
- sub?: string
17
- value?: string
18
- series?: ChartLineSeries[]
19
- labels?: string[]
20
- xLabels?: string[]
21
- max?: number
22
- rows?: number
23
- variant?: 'line' | 'area' | 'spark'
24
- dots?: boolean
25
- label?: string
26
- }>(),
27
- {
28
- title: 'Visitors',
29
- sub: 'January – June 2026',
30
- series: () => [
31
- { name: 'Desktop', values: [186, 305, 237, 73, 209, 214] },
32
- { name: 'Mobile', values: [80, 200, 120, 190, 130, 140] },
33
- ],
34
- rows: 4,
35
- variant: 'line',
36
- dots: true,
37
- label: 'Visitors per month',
38
- },
39
- )
40
- const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June']
41
- const labels = computed(() => props.labels ?? MONTHS)
42
- const axis = computed(() => props.xLabels ?? props.labels ?? MONTHS.map((m) => m.slice(0, 3)))
43
- const top = computed(() => props.max ?? niceMax(Math.max(0, ...props.series.flatMap((s) => s.values)), props.rows))
44
- const count = computed(() => Math.max(0, ...props.series.map((s) => s.values.length)))
45
- const paths = computed(() => props.series.map((s) => chartLinePath(s.values, top.value)))
46
- const yLabels = computed(() => Array.from({ length: props.rows + 1 }, (_, i) => top.value - (top.value / props.rows) * i))
47
- const colLabel = (i: number) => `${labels.value[i] ?? i + 1}: ${props.series.map((s) => `${s.name} ${s.values[i]}`).join(', ')}`
48
- </script>
49
-
50
- <template>
51
- <figure :class="['oc-chart-line', variant !== 'line' && `oc-chart-line--${variant}`, dots && 'oc-chart-line--dots']">
52
- <figcaption v-if="title || sub || value" class="oc-chart-line__head">
53
- <span v-if="title" class="oc-chart-line__title">{{ title }}</span>
54
- <span v-if="sub" class="oc-chart-line__sub">{{ sub }}</span>
55
- <span v-if="value" class="oc-chart-line__value">{{ value }}</span>
56
- </figcaption>
57
- <div class="oc-chart-line__body">
58
- <ul class="oc-chart-line__y" aria-hidden="true"><li v-for="(y, i) in yLabels" :key="i">{{ y }}</li></ul>
59
- <div class="oc-chart-line__plot" :style="rows !== 4 ? `--oc-chart-line-rows:${rows}` : undefined">
60
- <svg class="oc-chart-line__svg" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
61
- <g v-for="(s, i) in series" :key="i" class="oc-chart-line__series">
62
- <path class="oc-chart-line__area" :d="`${paths[i]} L100 100 L0 100 Z`" />
63
- <path :class="['oc-chart-line__path', s.dashed && 'oc-chart-line__path--dashed']" :d="paths[i]" />
64
- </g>
65
- </svg>
66
- <ul class="oc-chart-line__cols" :style="`--n:${count}`" :aria-label="label">
67
- <li v-for="i in count" :key="i" class="oc-chart-line__col" :style="`--x:${i - 1}`" tabindex="0" :aria-label="colLabel(i - 1)">
68
- <i v-for="(s, j) in series" :key="j" class="oc-chart-line__dot" :style="`--y:${n2((s.values[i - 1] / top) * 100)}%`"></i>
69
- <span class="oc-chart-line__tip" aria-hidden="true">
70
- <strong>{{ labels[i - 1] ?? i }}</strong>
71
- <span v-for="(s, j) in series" :key="j" class="oc-chart-line__row">{{ s.name }}<b>{{ s.values[i - 1] }}</b></span>
72
- </span>
73
- </li>
74
- </ul>
75
- </div>
76
- <ol class="oc-chart-line__x" aria-hidden="true"><li v-for="(l, i) in axis" :key="i">{{ l }}</li></ol>
77
- </div>
78
- <ul class="oc-chart-legend"><li v-for="(s, i) in series" :key="i"><span class="oc-chart-swatch"></span>{{ s.name }}</li></ul>
79
- </figure>
80
- </template>
1
+ <!-- File: packages/vue/src/components/OcChartLine.vue | Map: props + demo data -> computed axis / paths -> figure (head, y axis, plot: svg + hover columns, x axis, legend) -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Line / area chart in inline SVG (same markup as snippets/chart-line.html); tooltips open on :hover / :focus-visible (CSS only).
5
+ * series: { name, values, dashed? }[]; labels = one per point (tooltips), xLabels = short axis labels. max defaults to a round
6
+ * number above the data. variant: line | area | spark; dots = always visible. Classes: .oc-chart-line(--area/--dots/--spark) __head
7
+ * __title __sub __value __body __y __plot __svg __series __area __path(--dashed) __cols __col __dot __tip __row __x; .oc-chart-legend
8
+ */
9
+ import { computed } from 'vue'
10
+ import { chartLinePath, n2, niceMax } from '../composables/chartLine'
11
+
12
+ export interface ChartLineSeries { name: string; values: number[]; dashed?: boolean }
13
+ const props = withDefaults(
14
+ defineProps<{
15
+ title?: string
16
+ sub?: string
17
+ value?: string
18
+ series?: ChartLineSeries[]
19
+ labels?: string[]
20
+ xLabels?: string[]
21
+ max?: number
22
+ rows?: number
23
+ variant?: 'line' | 'area' | 'spark'
24
+ dots?: boolean
25
+ label?: string
26
+ }>(),
27
+ {
28
+ title: 'Visitors',
29
+ sub: 'January – June 2026',
30
+ series: () => [
31
+ { name: 'Desktop', values: [186, 305, 237, 73, 209, 214] },
32
+ { name: 'Mobile', values: [80, 200, 120, 190, 130, 140] },
33
+ ],
34
+ rows: 4,
35
+ variant: 'line',
36
+ dots: true,
37
+ label: 'Visitors per month',
38
+ },
39
+ )
40
+ const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June']
41
+ const labels = computed(() => props.labels ?? MONTHS)
42
+ const axis = computed(() => props.xLabels ?? props.labels ?? MONTHS.map((m) => m.slice(0, 3)))
43
+ const top = computed(() => props.max ?? niceMax(Math.max(0, ...props.series.flatMap((s) => s.values)), props.rows))
44
+ const count = computed(() => Math.max(0, ...props.series.map((s) => s.values.length)))
45
+ const paths = computed(() => props.series.map((s) => chartLinePath(s.values, top.value)))
46
+ const yLabels = computed(() => Array.from({ length: props.rows + 1 }, (_, i) => top.value - (top.value / props.rows) * i))
47
+ const colLabel = (i: number) => `${labels.value[i] ?? i + 1}: ${props.series.map((s) => `${s.name} ${s.values[i]}`).join(', ')}`
48
+ </script>
49
+
50
+ <template>
51
+ <figure :class="['oc-chart-line', variant !== 'line' && `oc-chart-line--${variant}`, dots && 'oc-chart-line--dots']">
52
+ <figcaption v-if="title || sub || value" class="oc-chart-line__head">
53
+ <span v-if="title" class="oc-chart-line__title">{{ title }}</span>
54
+ <span v-if="sub" class="oc-chart-line__sub">{{ sub }}</span>
55
+ <span v-if="value" class="oc-chart-line__value">{{ value }}</span>
56
+ </figcaption>
57
+ <div class="oc-chart-line__body">
58
+ <ul class="oc-chart-line__y" aria-hidden="true"><li v-for="(y, i) in yLabels" :key="i">{{ y }}</li></ul>
59
+ <div class="oc-chart-line__plot" :style="rows !== 4 ? `--oc-chart-line-rows:${rows}` : undefined">
60
+ <svg class="oc-chart-line__svg" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
61
+ <g v-for="(s, i) in series" :key="i" class="oc-chart-line__series">
62
+ <path class="oc-chart-line__area" :d="`${paths[i]} L100 100 L0 100 Z`" />
63
+ <path :class="['oc-chart-line__path', s.dashed && 'oc-chart-line__path--dashed']" :d="paths[i]" />
64
+ </g>
65
+ </svg>
66
+ <ul class="oc-chart-line__cols" :style="`--n:${count}`" :aria-label="label">
67
+ <li v-for="i in count" :key="i" class="oc-chart-line__col" :style="`--x:${i - 1}`" tabindex="0" :aria-label="colLabel(i - 1)">
68
+ <i v-for="(s, j) in series" :key="j" class="oc-chart-line__dot" :style="`--y:${n2((s.values[i - 1] / top) * 100)}%`"></i>
69
+ <span class="oc-chart-line__tip" aria-hidden="true">
70
+ <strong>{{ labels[i - 1] ?? i }}</strong>
71
+ <span v-for="(s, j) in series" :key="j" class="oc-chart-line__row">{{ s.name }}<b>{{ s.values[i - 1] }}</b></span>
72
+ </span>
73
+ </li>
74
+ </ul>
75
+ </div>
76
+ <ol class="oc-chart-line__x" aria-hidden="true"><li v-for="(l, i) in axis" :key="i">{{ l }}</li></ol>
77
+ </div>
78
+ <ul class="oc-chart-legend"><li v-for="(s, i) in series" :key="i"><span class="oc-chart-swatch"></span>{{ s.name }}</li></ul>
79
+ </figure>
80
+ </template>
@@ -1,42 +1,42 @@
1
- <!-- File: packages/vue/src/components/OcChatComposer.vue | Map: props + v-model -> form -> label + textarea -> bar (tools slot, hint, send) -->
2
- <script setup lang="ts">
3
- /**
4
- * Chat composer (same markup as snippets/chat-composer.html). v-model = text. Emits send(text) on submit
5
- * (then clears the field). Enter sends, Shift+Enter adds a line (sendOnEnter, default true).
6
- * Props: placeholder, label, hint, inline, disabled. Slot: tools (.oc-chat-composer__tool buttons).
7
- * Classes: .oc-chat-composer(--inline), __input, __bar, __tools, __tool, __hint, __send
8
- */
9
- import { useUid } from '../composables/useUid'
10
-
11
- const props = withDefaults(
12
- defineProps<{ placeholder?: string; label?: string; hint?: string; inline?: boolean; sendOnEnter?: boolean; disabled?: boolean }>(),
13
- { placeholder: 'Write a message…', label: 'Message', sendOnEnter: true },
14
- )
15
- const text = defineModel<string>({ default: '' })
16
- const emit = defineEmits<{ send: [text: string] }>()
17
- const id = useUid('oc-chat')
18
-
19
- function submit() {
20
- if (!text.value.trim() || props.disabled) return
21
- emit('send', text.value.trim())
22
- text.value = ''
23
- }
24
- function onKeydown(e: KeyboardEvent) {
25
- if (props.sendOnEnter && e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
26
- e.preventDefault()
27
- ;(e.target as HTMLTextAreaElement).form?.requestSubmit()
28
- }
29
- }
30
- </script>
31
-
32
- <template>
33
- <form :class="['oc-chat-composer', inline && 'oc-chat-composer--inline']" @submit.prevent="submit">
34
- <label class="oc-visually-hidden" :for="id">{{ label }}</label>
35
- <textarea :id="id" v-model="text" class="oc-chat-composer__input" rows="1" :placeholder="placeholder" required :disabled="disabled" @keydown="onKeydown" />
36
- <div class="oc-chat-composer__bar">
37
- <div v-if="$slots.tools" class="oc-chat-composer__tools"><slot name="tools" /></div>
38
- <span v-if="hint" class="oc-chat-composer__hint">{{ hint }}</span>
39
- <button type="submit" class="oc-chat-composer__send" aria-label="Send" :disabled="disabled">&#10148;</button>
40
- </div>
41
- </form>
42
- </template>
1
+ <!-- File: packages/vue/src/components/OcChatComposer.vue | Map: props + v-model -> form -> label + textarea -> bar (tools slot, hint, send) -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Chat composer (same markup as snippets/chat-composer.html). v-model = text. Emits send(text) on submit
5
+ * (then clears the field). Enter sends, Shift+Enter adds a line (sendOnEnter, default true).
6
+ * Props: placeholder, label, hint, inline, disabled. Slot: tools (.oc-chat-composer__tool buttons).
7
+ * Classes: .oc-chat-composer(--inline), __input, __bar, __tools, __tool, __hint, __send
8
+ */
9
+ import { useUid } from '../composables/useUid'
10
+
11
+ const props = withDefaults(
12
+ defineProps<{ placeholder?: string; label?: string; hint?: string; inline?: boolean; sendOnEnter?: boolean; disabled?: boolean }>(),
13
+ { placeholder: 'Write a message…', label: 'Message', sendOnEnter: true },
14
+ )
15
+ const text = defineModel<string>({ default: '' })
16
+ const emit = defineEmits<{ send: [text: string] }>()
17
+ const id = useUid('oc-chat')
18
+
19
+ function submit() {
20
+ if (!text.value.trim() || props.disabled) return
21
+ emit('send', text.value.trim())
22
+ text.value = ''
23
+ }
24
+ function onKeydown(e: KeyboardEvent) {
25
+ if (props.sendOnEnter && e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
26
+ e.preventDefault()
27
+ ;(e.target as HTMLTextAreaElement).form?.requestSubmit()
28
+ }
29
+ }
30
+ </script>
31
+
32
+ <template>
33
+ <form :class="['oc-chat-composer', inline && 'oc-chat-composer--inline']" @submit.prevent="submit">
34
+ <label class="oc-visually-hidden" :for="id">{{ label }}</label>
35
+ <textarea :id="id" v-model="text" class="oc-chat-composer__input" rows="1" :placeholder="placeholder" required :disabled="disabled" @keydown="onKeydown" />
36
+ <div class="oc-chat-composer__bar">
37
+ <div v-if="$slots.tools" class="oc-chat-composer__tools"><slot name="tools" /></div>
38
+ <span v-if="hint" class="oc-chat-composer__hint">{{ hint }}</span>
39
+ <button type="submit" class="oc-chat-composer__send" aria-label="Send" :disabled="disabled">&#10148;</button>
40
+ </div>
41
+ </form>
42
+ </template>
@@ -1,26 +1,26 @@
1
- <script setup lang="ts">
2
- /**
3
- * Checkbox or radio inside a label. v-model: boolean (checkbox), array (checkbox group with `value`)
4
- * or the selected `value` (radio). Attributes other than class/style go to the input.
5
- */
6
- import { useAttrsSplit } from '../composables/useAttrsSplit'
7
-
8
- defineOptions({ inheritAttrs: false })
9
- withDefaults(
10
- defineProps<{
11
- type?: 'checkbox' | 'radio'
12
- value?: unknown
13
- label?: string
14
- }>(),
15
- { type: 'checkbox' },
16
- )
17
- const model = defineModel<any>()
18
- const { root, control } = useAttrsSplit()
19
- </script>
20
-
21
- <template>
22
- <label v-bind="root">
23
- <input v-bind="control" v-model="model" :class="type === 'radio' ? 'oc-radio' : 'oc-checkbox'" :type="type" :value="value" />
24
- <slot><template v-if="label"> {{ label }}</template></slot>
25
- </label>
26
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Checkbox or radio inside a label. v-model: boolean (checkbox), array (checkbox group with `value`)
4
+ * or the selected `value` (radio). Attributes other than class/style go to the input.
5
+ */
6
+ import { useAttrsSplit } from '../composables/useAttrsSplit'
7
+
8
+ defineOptions({ inheritAttrs: false })
9
+ withDefaults(
10
+ defineProps<{
11
+ type?: 'checkbox' | 'radio'
12
+ value?: unknown
13
+ label?: string
14
+ }>(),
15
+ { type: 'checkbox' },
16
+ )
17
+ const model = defineModel<any>()
18
+ const { root, control } = useAttrsSplit()
19
+ </script>
20
+
21
+ <template>
22
+ <label v-bind="root">
23
+ <input v-bind="control" v-model="model" :class="type === 'radio' ? 'oc-radio' : 'oc-checkbox'" :type="type" :value="value" />
24
+ <slot><template v-if="label"> {{ label }}</template></slot>
25
+ </label>
26
+ </template>
@@ -1,29 +1,29 @@
1
- <script setup lang="ts">
2
- /**
3
- * Rule / step list with met and unmet states. Classes: .oc-checklist(--card), .oc-checklist-item.is-done
4
- * Slots: default (replaces all rows), action (scoped: item) in the card variant.
5
- */
6
- export interface ChecklistItem {
7
- title: string
8
- hint?: string
9
- done?: boolean
10
- }
11
- withDefaults(defineProps<{ items?: ChecklistItem[]; card?: boolean; label?: string }>(), { items: () => [] })
12
- </script>
13
-
14
- <template>
15
- <ul :class="['oc-checklist', card && 'oc-checklist--card']" aria-live="polite" :aria-label="label">
16
- <slot>
17
- <li v-for="(item, i) in items" :key="i" :class="['oc-checklist-item', item.done && 'is-done']">
18
- <template v-if="card">
19
- <span class="oc-checklist-text">
20
- <span class="oc-checklist-title">{{ item.title }}</span>
21
- <span v-if="item.hint" class="oc-checklist-hint">{{ item.hint }}</span>
22
- </span>
23
- <slot name="action" :item="item" />
24
- </template>
25
- <template v-else>{{ item.title }}</template>
26
- </li>
27
- </slot>
28
- </ul>
29
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Rule / step list with met and unmet states. Classes: .oc-checklist(--card), .oc-checklist-item.is-done
4
+ * Slots: default (replaces all rows), action (scoped: item) in the card variant.
5
+ */
6
+ export interface ChecklistItem {
7
+ title: string
8
+ hint?: string
9
+ done?: boolean
10
+ }
11
+ withDefaults(defineProps<{ items?: ChecklistItem[]; card?: boolean; label?: string }>(), { items: () => [] })
12
+ </script>
13
+
14
+ <template>
15
+ <ul :class="['oc-checklist', card && 'oc-checklist--card']" aria-live="polite" :aria-label="label">
16
+ <slot>
17
+ <li v-for="(item, i) in items" :key="i" :class="['oc-checklist-item', item.done && 'is-done']">
18
+ <template v-if="card">
19
+ <span class="oc-checklist-text">
20
+ <span class="oc-checklist-title">{{ item.title }}</span>
21
+ <span v-if="item.hint" class="oc-checklist-hint">{{ item.hint }}</span>
22
+ </span>
23
+ <slot name="action" :item="item" />
24
+ </template>
25
+ <template v-else>{{ item.title }}</template>
26
+ </li>
27
+ </slot>
28
+ </ul>
29
+ </template>