@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/OcNextTeaser.vue | Map: props -> a.oc-next-teaser -> label -> name (mark slot) -> tagline -->
2
- <script setup lang="ts">
3
- /**
4
- * "Next product" teaser card. Slots: mark (e.g. an OcProductMark size="sm"), name, tagline.
5
- * Classes: .oc-next-teaser (--prev), __label, __name, __tagline
6
- */
7
- withDefaults(
8
- defineProps<{
9
- href: string
10
- label?: string
11
- name?: string
12
- tagline?: string
13
- prev?: boolean
14
- }>(),
15
- { label: 'Next product' },
16
- )
17
- </script>
18
-
19
- <template>
20
- <a :class="['oc-next-teaser', prev && 'oc-next-teaser--prev']" :href="href">
21
- <span v-if="label" class="oc-next-teaser__label">{{ label }}</span>
22
- <span class="oc-next-teaser__name"><slot name="mark" /><slot name="name">{{ name }}</slot></span>
23
- <span v-if="tagline || $slots.tagline" class="oc-next-teaser__tagline"><slot name="tagline">{{ tagline }}</slot></span>
24
- </a>
25
- </template>
1
+ <!-- File: packages/vue/src/components/OcNextTeaser.vue | Map: props -> a.oc-next-teaser -> label -> name (mark slot) -> tagline -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * "Next product" teaser card. Slots: mark (e.g. an OcProductMark size="sm"), name, tagline.
5
+ * Classes: .oc-next-teaser (--prev), __label, __name, __tagline
6
+ */
7
+ withDefaults(
8
+ defineProps<{
9
+ href: string
10
+ label?: string
11
+ name?: string
12
+ tagline?: string
13
+ prev?: boolean
14
+ }>(),
15
+ { label: 'Next product' },
16
+ )
17
+ </script>
18
+
19
+ <template>
20
+ <a :class="['oc-next-teaser', prev && 'oc-next-teaser--prev']" :href="href">
21
+ <span v-if="label" class="oc-next-teaser__label">{{ label }}</span>
22
+ <span class="oc-next-teaser__name"><slot name="mark" /><slot name="name">{{ name }}</slot></span>
23
+ <span v-if="tagline || $slots.tagline" class="oc-next-teaser__tagline"><slot name="tagline">{{ tagline }}</slot></span>
24
+ </a>
25
+ </template>
@@ -1,47 +1,47 @@
1
- <!-- File: packages/vue/src/components/OcNumberInput.vue | Map: props + model -> step helper -> template (minus, number field, plus) -->
2
- <script setup lang="ts">
3
- /**
4
- * Number field with - / + buttons. v-model: number | null. Buttons disable themselves at min / max.
5
- * Same markup as packages/ui/snippets/number-input.html (no page script needed here).
6
- * class/style stay on the frame, other attributes (id, name, aria-label) go to the input.
7
- */
8
- import { useAttrsSplit } from '../composables/useAttrsSplit'
9
-
10
- defineOptions({ inheritAttrs: false })
11
- const props = withDefaults(
12
- defineProps<{ min?: number; max?: number; step?: number; size?: 'sm' | 'md'; block?: boolean; error?: boolean; disabled?: boolean; decrementLabel?: string; incrementLabel?: string }>(),
13
- { step: 1, size: 'md', decrementLabel: 'Decrease', incrementLabel: 'Increase' },
14
- )
15
- const model = defineModel<number | null>({ default: null })
16
- const { root, control } = useAttrsSplit()
17
-
18
- /** Add one step, keep the result inside min/max and drop float noise (0.1 + 0.2). */
19
- function stepBy(dir: 1 | -1) {
20
- const next = Math.round(((model.value ?? props.min ?? 0) + dir * props.step) * 1e6) / 1e6
21
- model.value = Math.min(props.max ?? Infinity, Math.max(props.min ?? -Infinity, next))
22
- }
23
-
24
- function onInput(e: Event) {
25
- const raw = (e.target as HTMLInputElement).value
26
- model.value = raw === '' ? null : Number(raw)
27
- }
28
- </script>
29
-
30
- <template>
31
- <div v-bind="root" :class="['oc-number-input', size === 'sm' && 'oc-number-input--sm', block && 'oc-number-input--block', error && 'oc-number-input--error']">
32
- <button class="oc-number-input-step" type="button" :aria-label="decrementLabel" :disabled="disabled || (min != null && model != null && model <= min)" @click="stepBy(-1)">&minus;</button>
33
- <input
34
- v-bind="control"
35
- class="oc-number-input-field"
36
- type="number"
37
- :min="min"
38
- :max="max"
39
- :step="step"
40
- :disabled="disabled"
41
- :aria-invalid="error ? 'true' : undefined"
42
- :value="model ?? ''"
43
- @input="onInput"
44
- />
45
- <button class="oc-number-input-step" type="button" :aria-label="incrementLabel" :disabled="disabled || (max != null && model != null && model >= max)" @click="stepBy(1)">+</button>
46
- </div>
47
- </template>
1
+ <!-- File: packages/vue/src/components/OcNumberInput.vue | Map: props + model -> step helper -> template (minus, number field, plus) -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Number field with - / + buttons. v-model: number | null. Buttons disable themselves at min / max.
5
+ * Same markup as packages/ui/snippets/number-input.html (no page script needed here).
6
+ * class/style stay on the frame, other attributes (id, name, aria-label) go to the input.
7
+ */
8
+ import { useAttrsSplit } from '../composables/useAttrsSplit'
9
+
10
+ defineOptions({ inheritAttrs: false })
11
+ const props = withDefaults(
12
+ defineProps<{ min?: number; max?: number; step?: number; size?: 'sm' | 'md'; block?: boolean; error?: boolean; disabled?: boolean; decrementLabel?: string; incrementLabel?: string }>(),
13
+ { step: 1, size: 'md', decrementLabel: 'Decrease', incrementLabel: 'Increase' },
14
+ )
15
+ const model = defineModel<number | null>({ default: null })
16
+ const { root, control } = useAttrsSplit()
17
+
18
+ /** Add one step, keep the result inside min/max and drop float noise (0.1 + 0.2). */
19
+ function stepBy(dir: 1 | -1) {
20
+ const next = Math.round(((model.value ?? props.min ?? 0) + dir * props.step) * 1e6) / 1e6
21
+ model.value = Math.min(props.max ?? Infinity, Math.max(props.min ?? -Infinity, next))
22
+ }
23
+
24
+ function onInput(e: Event) {
25
+ const raw = (e.target as HTMLInputElement).value
26
+ model.value = raw === '' ? null : Number(raw)
27
+ }
28
+ </script>
29
+
30
+ <template>
31
+ <div v-bind="root" :class="['oc-number-input', size === 'sm' && 'oc-number-input--sm', block && 'oc-number-input--block', error && 'oc-number-input--error']">
32
+ <button class="oc-number-input-step" type="button" :aria-label="decrementLabel" :disabled="disabled || (min != null && model != null && model <= min)" @click="stepBy(-1)">&minus;</button>
33
+ <input
34
+ v-bind="control"
35
+ class="oc-number-input-field"
36
+ type="number"
37
+ :min="min"
38
+ :max="max"
39
+ :step="step"
40
+ :disabled="disabled"
41
+ :aria-invalid="error ? 'true' : undefined"
42
+ :value="model ?? ''"
43
+ @input="onInput"
44
+ />
45
+ <button class="oc-number-input-step" type="button" :aria-label="incrementLabel" :disabled="disabled || (max != null && model != null && model >= max)" @click="stepBy(1)">+</button>
46
+ </div>
47
+ </template>
@@ -1,34 +1,34 @@
1
- <script setup lang="ts">
2
- /**
3
- * One-time code field drawn as separate cells on a single real input.
4
- * v-model: the code string. `length` sets maxlength and --oc-otp-length. `invalid` sets aria-invalid.
5
- * Emits `complete` with the code when it reaches `length`.
6
- */
7
- import { computed, watch } from 'vue'
8
-
9
- const props = withDefaults(defineProps<{ length?: number; invalid?: boolean; numeric?: boolean }>(), {
10
- length: 6,
11
- numeric: true,
12
- })
13
- const emit = defineEmits<{ complete: [code: string] }>()
14
- const model = defineModel<string>({ default: '' })
15
- const style = computed(() => ({ '--oc-otp-length': String(props.length) }))
16
-
17
- watch(model, (code) => {
18
- if (code.length === props.length) emit('complete', code)
19
- })
20
- </script>
21
-
22
- <template>
23
- <input
24
- v-model="model"
25
- class="oc-otp"
26
- type="text"
27
- :inputmode="numeric ? 'numeric' : 'text'"
28
- autocomplete="one-time-code"
29
- :maxlength="length"
30
- :pattern="numeric ? '[0-9]*' : undefined"
31
- :aria-invalid="invalid ? 'true' : undefined"
32
- :style="style"
33
- />
34
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * One-time code field drawn as separate cells on a single real input.
4
+ * v-model: the code string. `length` sets maxlength and --oc-otp-length. `invalid` sets aria-invalid.
5
+ * Emits `complete` with the code when it reaches `length`.
6
+ */
7
+ import { computed, watch } from 'vue'
8
+
9
+ const props = withDefaults(defineProps<{ length?: number; invalid?: boolean; numeric?: boolean }>(), {
10
+ length: 6,
11
+ numeric: true,
12
+ })
13
+ const emit = defineEmits<{ complete: [code: string] }>()
14
+ const model = defineModel<string>({ default: '' })
15
+ const style = computed(() => ({ '--oc-otp-length': String(props.length) }))
16
+
17
+ watch(model, (code) => {
18
+ if (code.length === props.length) emit('complete', code)
19
+ })
20
+ </script>
21
+
22
+ <template>
23
+ <input
24
+ v-model="model"
25
+ class="oc-otp"
26
+ type="text"
27
+ :inputmode="numeric ? 'numeric' : 'text'"
28
+ autocomplete="one-time-code"
29
+ :maxlength="length"
30
+ :pattern="numeric ? '[0-9]*' : undefined"
31
+ :aria-invalid="invalid ? 'true' : undefined"
32
+ :style="style"
33
+ />
34
+ </template>
@@ -1,34 +1,34 @@
1
- <!-- File: packages/vue/src/components/OcPageHeader.vue | Map: header -> back slot -> main (text: title, subtitle, meta slot | actions slot) -->
2
- <script setup lang="ts">
3
- /**
4
- * Title row of an app page (size container: actions sit next to the title from a 36rem header width).
5
- * Slots: back, title, subtitle, meta, actions.
6
- * Classes: .oc-page-header(--bordered/--sticky/--sm), -back, -main, -text, -title, -subtitle, -meta, -actions
7
- */
8
- withDefaults(
9
- defineProps<{
10
- title?: string
11
- subtitle?: string
12
- /** Heading level of the title (default 1). */
13
- level?: 1 | 2 | 3
14
- bordered?: boolean
15
- sticky?: boolean
16
- size?: 'sm'
17
- }>(),
18
- { level: 1 },
19
- )
20
- </script>
21
-
22
- <template>
23
- <header :class="['oc-page-header', bordered && 'oc-page-header--bordered', sticky && 'oc-page-header--sticky', size && `oc-page-header--${size}`]">
24
- <div v-if="$slots.back" class="oc-page-header-back"><slot name="back" /></div>
25
- <div class="oc-page-header-main">
26
- <div class="oc-page-header-text">
27
- <component :is="`h${level}`" class="oc-page-header-title"><slot name="title">{{ title }}</slot></component>
28
- <p v-if="$slots.subtitle || subtitle" class="oc-page-header-subtitle"><slot name="subtitle">{{ subtitle }}</slot></p>
29
- <div v-if="$slots.meta" class="oc-page-header-meta"><slot name="meta" /></div>
30
- </div>
31
- <div v-if="$slots.actions" class="oc-page-header-actions"><slot name="actions" /></div>
32
- </div>
33
- </header>
34
- </template>
1
+ <!-- File: packages/vue/src/components/OcPageHeader.vue | Map: header -> back slot -> main (text: title, subtitle, meta slot | actions slot) -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Title row of an app page (size container: actions sit next to the title from a 36rem header width).
5
+ * Slots: back, title, subtitle, meta, actions.
6
+ * Classes: .oc-page-header(--bordered/--sticky/--sm), -back, -main, -text, -title, -subtitle, -meta, -actions
7
+ */
8
+ withDefaults(
9
+ defineProps<{
10
+ title?: string
11
+ subtitle?: string
12
+ /** Heading level of the title (default 1). */
13
+ level?: 1 | 2 | 3
14
+ bordered?: boolean
15
+ sticky?: boolean
16
+ size?: 'sm'
17
+ }>(),
18
+ { level: 1 },
19
+ )
20
+ </script>
21
+
22
+ <template>
23
+ <header :class="['oc-page-header', bordered && 'oc-page-header--bordered', sticky && 'oc-page-header--sticky', size && `oc-page-header--${size}`]">
24
+ <div v-if="$slots.back" class="oc-page-header-back"><slot name="back" /></div>
25
+ <div class="oc-page-header-main">
26
+ <div class="oc-page-header-text">
27
+ <component :is="`h${level}`" class="oc-page-header-title"><slot name="title">{{ title }}</slot></component>
28
+ <p v-if="$slots.subtitle || subtitle" class="oc-page-header-subtitle"><slot name="subtitle">{{ subtitle }}</slot></p>
29
+ <div v-if="$slots.meta" class="oc-page-header-meta"><slot name="meta" /></div>
30
+ </div>
31
+ <div v-if="$slots.actions" class="oc-page-header-actions"><slot name="actions" /></div>
32
+ </div>
33
+ </header>
34
+ </template>
@@ -1,33 +1,33 @@
1
- <script setup lang="ts">
2
- /**
3
- * Pagination. v-model = current page (1-based), `pages` = page count. Links point to
4
- * `hrefFor(page)` (default ?page=n); without a custom hrefFor, clicks update the model instead of navigating.
5
- */
6
- const props = withDefaults(
7
- defineProps<{
8
- pages: number
9
- hrefFor?: (page: number) => string
10
- label?: string
11
- }>(),
12
- { label: 'Pagination' },
13
- )
14
- const page = defineModel<number>({ default: 1 })
15
-
16
- function go(e: Event, n: number) {
17
- if (!props.hrefFor) e.preventDefault()
18
- page.value = n
19
- }
20
- </script>
21
-
22
- <template>
23
- <nav class="oc-pagination" :aria-label="label">
24
- <a
25
- v-for="n in pages"
26
- :key="n"
27
- class="oc-pagination-item"
28
- :href="hrefFor ? hrefFor(n) : `?page=${n}`"
29
- :aria-current="n === page ? 'page' : undefined"
30
- @click="go($event, n)"
31
- >{{ n }}</a>
32
- </nav>
33
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Pagination. v-model = current page (1-based), `pages` = page count. Links point to
4
+ * `hrefFor(page)` (default ?page=n); without a custom hrefFor, clicks update the model instead of navigating.
5
+ */
6
+ const props = withDefaults(
7
+ defineProps<{
8
+ pages: number
9
+ hrefFor?: (page: number) => string
10
+ label?: string
11
+ }>(),
12
+ { label: 'Pagination' },
13
+ )
14
+ const page = defineModel<number>({ default: 1 })
15
+
16
+ function go(e: Event, n: number) {
17
+ if (!props.hrefFor) e.preventDefault()
18
+ page.value = n
19
+ }
20
+ </script>
21
+
22
+ <template>
23
+ <nav class="oc-pagination" :aria-label="label">
24
+ <a
25
+ v-for="n in pages"
26
+ :key="n"
27
+ class="oc-pagination-item"
28
+ :href="hrefFor ? hrefFor(n) : `?page=${n}`"
29
+ :aria-current="n === page ? 'page' : undefined"
30
+ @click="go($event, n)"
31
+ >{{ n }}</a>
32
+ </nav>
33
+ </template>
@@ -1,27 +1,27 @@
1
- <script setup lang="ts">
2
- /**
3
- * Password field with show/hide button (aria-pressed reflects the state). v-model.
4
- * class/style stay on the wrapper, every other attribute (id, name, autocomplete...) goes to the input.
5
- */
6
- import { ref } from 'vue'
7
- import { useAttrsSplit } from '../composables/useAttrsSplit'
8
-
9
- defineOptions({ inheritAttrs: false })
10
- withDefaults(defineProps<{ showLabel?: string; hideLabel?: string; toggleLabel?: string }>(), {
11
- showLabel: 'Show',
12
- hideLabel: 'Hide',
13
- toggleLabel: 'Toggle password visibility',
14
- })
15
- const model = defineModel<string>({ default: '' })
16
- const { root, control } = useAttrsSplit()
17
- const visible = ref(false)
18
- </script>
19
-
20
- <template>
21
- <div v-bind="root" class="oc-input-reveal">
22
- <input v-bind="control" v-model="model" class="oc-input" :type="visible ? 'text' : 'password'" />
23
- <button class="oc-input-reveal-button" type="button" :aria-pressed="visible ? 'true' : 'false'" :aria-label="toggleLabel" @click="visible = !visible">
24
- {{ visible ? hideLabel : showLabel }}
25
- </button>
26
- </div>
27
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Password field with show/hide button (aria-pressed reflects the state). v-model.
4
+ * class/style stay on the wrapper, every other attribute (id, name, autocomplete...) goes to the input.
5
+ */
6
+ import { ref } from 'vue'
7
+ import { useAttrsSplit } from '../composables/useAttrsSplit'
8
+
9
+ defineOptions({ inheritAttrs: false })
10
+ withDefaults(defineProps<{ showLabel?: string; hideLabel?: string; toggleLabel?: string }>(), {
11
+ showLabel: 'Show',
12
+ hideLabel: 'Hide',
13
+ toggleLabel: 'Toggle password visibility',
14
+ })
15
+ const model = defineModel<string>({ default: '' })
16
+ const { root, control } = useAttrsSplit()
17
+ const visible = ref(false)
18
+ </script>
19
+
20
+ <template>
21
+ <div v-bind="root" class="oc-input-reveal">
22
+ <input v-bind="control" v-model="model" class="oc-input" :type="visible ? 'text' : 'password'" />
23
+ <button class="oc-input-reveal-button" type="button" :aria-pressed="visible ? 'true' : 'false'" :aria-label="toggleLabel" @click="visible = !visible">
24
+ {{ visible ? hideLabel : showLabel }}
25
+ </button>
26
+ </div>
27
+ </template>
@@ -1,28 +1,28 @@
1
- <!-- File: packages/vue/src/components/OcPasswordRequirements.vue | Map: rule type + defaults -> props -> template (live list, --met per rule) -->
2
- <script setup lang="ts">
3
- /**
4
- * Live password rule checklist. Pass the password; each rule turns green (--met) when it passes.
5
- * Same markup as packages/ui/snippets/password-requirements.html.
6
- */
7
- export interface PasswordRequirementRule {
8
- label: string
9
- /** True when the password meets the rule. */
10
- test: (password: string) => boolean
11
- }
12
-
13
- withDefaults(defineProps<{ password: string; rules?: PasswordRequirementRule[]; inline?: boolean; hideWhenEmpty?: boolean }>(), {
14
- // Common rules: length, uppercase, number, symbol.
15
- rules: () => [
16
- { label: 'At least 8 characters', test: (p: string) => p.length >= 8 },
17
- { label: 'One uppercase letter', test: (p: string) => /[A-Z]/.test(p) },
18
- { label: 'One number', test: (p: string) => /[0-9]/.test(p) },
19
- { label: 'One symbol', test: (p: string) => /[^A-Za-z0-9]/.test(p) },
20
- ],
21
- })
22
- </script>
23
-
24
- <template>
25
- <ul v-if="!hideWhenEmpty || password" :class="['oc-password-requirements', inline && 'oc-password-requirements--inline']" aria-live="polite">
26
- <li v-for="(rule, i) in rules" :key="i" :class="['oc-password-requirement', rule.test(password) && 'oc-password-requirement--met']">{{ rule.label }}</li>
27
- </ul>
28
- </template>
1
+ <!-- File: packages/vue/src/components/OcPasswordRequirements.vue | Map: rule type + defaults -> props -> template (live list, --met per rule) -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Live password rule checklist. Pass the password; each rule turns green (--met) when it passes.
5
+ * Same markup as packages/ui/snippets/password-requirements.html.
6
+ */
7
+ export interface PasswordRequirementRule {
8
+ label: string
9
+ /** True when the password meets the rule. */
10
+ test: (password: string) => boolean
11
+ }
12
+
13
+ withDefaults(defineProps<{ password: string; rules?: PasswordRequirementRule[]; inline?: boolean; hideWhenEmpty?: boolean }>(), {
14
+ // Common rules: length, uppercase, number, symbol.
15
+ rules: () => [
16
+ { label: 'At least 8 characters', test: (p: string) => p.length >= 8 },
17
+ { label: 'One uppercase letter', test: (p: string) => /[A-Z]/.test(p) },
18
+ { label: 'One number', test: (p: string) => /[0-9]/.test(p) },
19
+ { label: 'One symbol', test: (p: string) => /[^A-Za-z0-9]/.test(p) },
20
+ ],
21
+ })
22
+ </script>
23
+
24
+ <template>
25
+ <ul v-if="!hideWhenEmpty || password" :class="['oc-password-requirements', inline && 'oc-password-requirements--inline']" aria-live="polite">
26
+ <li v-for="(rule, i) in rules" :key="i" :class="['oc-password-requirement', rule.test(password) && 'oc-password-requirement--met']">{{ rule.label }}</li>
27
+ </ul>
28
+ </template>
@@ -1,72 +1,72 @@
1
- <!-- File: packages/vue/src/components/OcPhoneInput.vue | Map: props + model -> split / group helpers -> template (dial select + tel input) -->
2
- <script setup lang="ts">
3
- /**
4
- * Phone field: dial code select + national number. v-model = full value without spaces ("+32470123456").
5
- * Same markup as packages/ui/snippets/phone-input.html. JS only groups digits ("470 12 34 56").
6
- * class/style stay on the frame, other attributes (id, name, placeholder, aria-*) go to the number input.
7
- */
8
- import { computed, ref } from 'vue'
9
- import { useAttrsSplit } from '../composables/useAttrsSplit'
10
-
11
- export interface PhoneInputCountry {
12
- dial: string
13
- label: string
14
- }
15
-
16
- defineOptions({ inheritAttrs: false })
17
- const props = withDefaults(
18
- defineProps<{ countries?: PhoneInputCountry[]; defaultDial?: string; size?: 'sm' | 'md'; error?: boolean; disabled?: boolean; dialLabel?: string }>(),
19
- {
20
- countries: () => [
21
- { dial: '+32', label: 'BE +32' },
22
- { dial: '+31', label: 'NL +31' },
23
- { dial: '+33', label: 'FR +33' },
24
- { dial: '+49', label: 'DE +49' },
25
- ],
26
- size: 'md',
27
- dialLabel: 'Country code',
28
- },
29
- )
30
- const model = defineModel<string>({ default: '' })
31
- const { root, control } = useAttrsSplit()
32
-
33
- // Longest dial code that the value starts with, e.g. "+32" from "+32470...".
34
- const parsed = computed(() => {
35
- const match = [...props.countries].sort((a, b) => b.dial.length - a.dial.length).find((c) => model.value.startsWith(c.dial))
36
- return match ? { dial: match.dial, national: model.value.slice(match.dial.length) } : { dial: '', national: model.value.replace(/^\+/, '') }
37
- })
38
- // Remember the chosen dial code while the number is still empty.
39
- const dialState = ref(parsed.value.dial || props.defaultDial || props.countries[0]?.dial || '')
40
- const dial = computed(() => parsed.value.dial || dialState.value)
41
-
42
- /** "470123456" -> "470 12 34 56": a group of 3, then pairs. */
43
- const grouped = computed(() => {
44
- const d = parsed.value.national.replace(/\D/g, '')
45
- return [d.slice(0, 3), ...(d.slice(3).match(/.{1,2}/g) ?? [])].filter(Boolean).join(' ')
46
- })
47
-
48
- function emit(nextDial: string, national: string) {
49
- dialState.value = nextDial
50
- const digits = national.replace(/\D/g, '')
51
- model.value = digits ? nextDial + digits : ''
52
- }
53
- </script>
54
-
55
- <template>
56
- <div v-bind="root" :class="['oc-phone-input', size === 'sm' && 'oc-phone-input--sm', error && 'oc-phone-input--error']">
57
- <select class="oc-phone-input-dial" :aria-label="dialLabel" :value="dial" :disabled="disabled" @change="emit(($event.target as HTMLSelectElement).value, parsed.national)">
58
- <option v-for="c in countries" :key="c.dial" :value="c.dial">{{ c.label }}</option>
59
- </select>
60
- <input
61
- v-bind="control"
62
- class="oc-phone-input-number"
63
- type="tel"
64
- inputmode="tel"
65
- autocomplete="tel-national"
66
- :aria-invalid="error ? 'true' : undefined"
67
- :disabled="disabled"
68
- :value="grouped"
69
- @input="emit(dial, ($event.target as HTMLInputElement).value)"
70
- />
71
- </div>
72
- </template>
1
+ <!-- File: packages/vue/src/components/OcPhoneInput.vue | Map: props + model -> split / group helpers -> template (dial select + tel input) -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Phone field: dial code select + national number. v-model = full value without spaces ("+32470123456").
5
+ * Same markup as packages/ui/snippets/phone-input.html. JS only groups digits ("470 12 34 56").
6
+ * class/style stay on the frame, other attributes (id, name, placeholder, aria-*) go to the number input.
7
+ */
8
+ import { computed, ref } from 'vue'
9
+ import { useAttrsSplit } from '../composables/useAttrsSplit'
10
+
11
+ export interface PhoneInputCountry {
12
+ dial: string
13
+ label: string
14
+ }
15
+
16
+ defineOptions({ inheritAttrs: false })
17
+ const props = withDefaults(
18
+ defineProps<{ countries?: PhoneInputCountry[]; defaultDial?: string; size?: 'sm' | 'md'; error?: boolean; disabled?: boolean; dialLabel?: string }>(),
19
+ {
20
+ countries: () => [
21
+ { dial: '+32', label: 'BE +32' },
22
+ { dial: '+31', label: 'NL +31' },
23
+ { dial: '+33', label: 'FR +33' },
24
+ { dial: '+49', label: 'DE +49' },
25
+ ],
26
+ size: 'md',
27
+ dialLabel: 'Country code',
28
+ },
29
+ )
30
+ const model = defineModel<string>({ default: '' })
31
+ const { root, control } = useAttrsSplit()
32
+
33
+ // Longest dial code that the value starts with, e.g. "+32" from "+32470...".
34
+ const parsed = computed(() => {
35
+ const match = [...props.countries].sort((a, b) => b.dial.length - a.dial.length).find((c) => model.value.startsWith(c.dial))
36
+ return match ? { dial: match.dial, national: model.value.slice(match.dial.length) } : { dial: '', national: model.value.replace(/^\+/, '') }
37
+ })
38
+ // Remember the chosen dial code while the number is still empty.
39
+ const dialState = ref(parsed.value.dial || props.defaultDial || props.countries[0]?.dial || '')
40
+ const dial = computed(() => parsed.value.dial || dialState.value)
41
+
42
+ /** "470123456" -> "470 12 34 56": a group of 3, then pairs. */
43
+ const grouped = computed(() => {
44
+ const d = parsed.value.national.replace(/\D/g, '')
45
+ return [d.slice(0, 3), ...(d.slice(3).match(/.{1,2}/g) ?? [])].filter(Boolean).join(' ')
46
+ })
47
+
48
+ function emit(nextDial: string, national: string) {
49
+ dialState.value = nextDial
50
+ const digits = national.replace(/\D/g, '')
51
+ model.value = digits ? nextDial + digits : ''
52
+ }
53
+ </script>
54
+
55
+ <template>
56
+ <div v-bind="root" :class="['oc-phone-input', size === 'sm' && 'oc-phone-input--sm', error && 'oc-phone-input--error']">
57
+ <select class="oc-phone-input-dial" :aria-label="dialLabel" :value="dial" :disabled="disabled" @change="emit(($event.target as HTMLSelectElement).value, parsed.national)">
58
+ <option v-for="c in countries" :key="c.dial" :value="c.dial">{{ c.label }}</option>
59
+ </select>
60
+ <input
61
+ v-bind="control"
62
+ class="oc-phone-input-number"
63
+ type="tel"
64
+ inputmode="tel"
65
+ autocomplete="tel-national"
66
+ :aria-invalid="error ? 'true' : undefined"
67
+ :disabled="disabled"
68
+ :value="grouped"
69
+ @input="emit(dial, ($event.target as HTMLInputElement).value)"
70
+ />
71
+ </div>
72
+ </template>