@birdycom/omniaui-vue 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +49 -49
- package/dist/omniaui-vue.js.map +1 -1
- package/package.json +69 -69
- package/src/blocks/OcBlockAboutTimeline.vue +44 -44
- package/src/blocks/OcBlockAppHeader.vue +36 -36
- package/src/blocks/OcBlockAppShell.vue +29 -29
- package/src/blocks/OcBlockAuth.vue +24 -24
- package/src/blocks/OcBlockAuthSplit.vue +47 -47
- package/src/blocks/OcBlockContactGrid.vue +53 -53
- package/src/blocks/OcBlockCta.vue +12 -12
- package/src/blocks/OcBlockDataTable.vue +27 -27
- package/src/blocks/OcBlockFaq.vue +49 -49
- package/src/blocks/OcBlockFeatures.vue +23 -23
- package/src/blocks/OcBlockFooter.vue +25 -25
- package/src/blocks/OcBlockFormPage.vue +24 -24
- package/src/blocks/OcBlockHero.vue +13 -13
- package/src/blocks/OcBlockHeroCode.vue +42 -42
- package/src/blocks/OcBlockHeroOrbit.vue +69 -69
- package/src/blocks/OcBlockHeroSplit.vue +15 -15
- package/src/blocks/OcBlockModuleHeader.vue +21 -21
- package/src/blocks/OcBlockPageHero.vue +13 -13
- package/src/blocks/OcBlockPortfolioGrid.vue +63 -63
- package/src/blocks/OcBlockPricing.vue +39 -39
- package/src/blocks/OcBlockProcess.vue +39 -39
- package/src/blocks/OcBlockProductGrid.vue +41 -41
- package/src/blocks/OcBlockProfileHeader.vue +50 -50
- package/src/blocks/OcBlockShowcase.vue +43 -43
- package/src/blocks/OcBlockSiteFooter.vue +54 -54
- package/src/blocks/OcBlockSiteHeader.vue +34 -34
- package/src/blocks/OcBlockSpotlight.vue +47 -47
- package/src/blocks/OcBlockStats.vue +17 -17
- package/src/blocks/OcBlockStatsCounter.vue +20 -20
- package/src/blocks/OcBlockTeamList.vue +53 -53
- package/src/blocks/OcBlockTwoFactor.vue +61 -61
- package/src/blocks/OcBlockWizardDialog.vue +67 -67
- package/src/components/OcAccordion.vue +12 -12
- package/src/components/OcAccordionAccent.vue +27 -27
- package/src/components/OcAccordionItem.vue +19 -19
- package/src/components/OcAffixInput.vue +40 -40
- package/src/components/OcAlert.vue +17 -17
- package/src/components/OcAlertDialog.vue +66 -66
- package/src/components/OcAlphaTabs.vue +54 -54
- package/src/components/OcAspectRatio.vue +21 -21
- package/src/components/OcAttachment.vue +59 -59
- package/src/components/OcAttachmentImage.vue +13 -13
- package/src/components/OcAttachmentList.vue +31 -31
- package/src/components/OcAttachmentThumb.vue +14 -14
- package/src/components/OcAvatar.vue +18 -18
- package/src/components/OcAvatarGroup.vue +7 -7
- package/src/components/OcBackLink.vue +8 -8
- package/src/components/OcBadge.vue +13 -13
- package/src/components/OcBarcodeInput.vue +51 -51
- package/src/components/OcBreadcrumb.vue +22 -22
- package/src/components/OcBrowserFrame.vue +33 -33
- package/src/components/OcBusyOverlay.vue +32 -32
- package/src/components/OcButton.vue +47 -47
- package/src/components/OcButtonGroup.vue +10 -10
- package/src/components/OcButtonGroupText.vue +7 -7
- package/src/components/OcCard.vue +20 -20
- package/src/components/OcChartLine.vue +80 -80
- package/src/components/OcChatComposer.vue +42 -42
- package/src/components/OcCheckbox.vue +26 -26
- package/src/components/OcChecklist.vue +29 -29
- package/src/components/OcCollapsible.vue +19 -19
- package/src/components/OcColorField.vue +15 -15
- package/src/components/OcColorSelect.vue +33 -33
- package/src/components/OcColumnMenu.vue +50 -50
- package/src/components/OcCommand.vue +119 -119
- package/src/components/OcConfirmDialog.vue +65 -65
- package/src/components/OcCookieConsent.vue +119 -119
- package/src/components/OcCopyField.vue +37 -37
- package/src/components/OcCounter.vue +15 -15
- package/src/components/OcDataTable.vue +144 -144
- package/src/components/OcDefinitionList.vue +35 -35
- package/src/components/OcDropdown.vue +23 -23
- package/src/components/OcDropdownItem.vue +9 -9
- package/src/components/OcDropdownLabel.vue +7 -7
- package/src/components/OcDropdownSeparator.vue +7 -7
- package/src/components/OcDropzone.vue +36 -36
- package/src/components/OcEmojiPicker.vue +45 -45
- package/src/components/OcEmptyState.vue +13 -13
- package/src/components/OcEntityCard.vue +47 -47
- package/src/components/OcFactList.vue +37 -37
- package/src/components/OcFilterCounts.vue +38 -38
- package/src/components/OcFlipCard.vue +36 -36
- package/src/components/OcFormField.vue +36 -36
- package/src/components/OcGreeting.vue +52 -52
- package/src/components/OcHeading.vue +19 -19
- package/src/components/OcHoverCard.vue +25 -25
- package/src/components/OcImageZoom.vue +39 -39
- package/src/components/OcInput.vue +9 -9
- package/src/components/OcInputGroup.vue +12 -12
- package/src/components/OcItem.vue +40 -40
- package/src/components/OcItemGroup.vue +7 -7
- package/src/components/OcItemSeparator.vue +7 -7
- package/src/components/OcKbd.vue +7 -7
- package/src/components/OcKbdGroup.vue +15 -15
- package/src/components/OcLabel.vue +10 -10
- package/src/components/OcLanguageSelect.vue +40 -40
- package/src/components/OcLauncherCard.vue +34 -34
- package/src/components/OcLightbox.vue +63 -63
- package/src/components/OcLinkArrow.vue +25 -25
- package/src/components/OcListToolbar.vue +44 -44
- package/src/components/OcMarquee.vue +36 -36
- package/src/components/OcMediaCard.vue +28 -28
- package/src/components/OcMediaRow.vue +28 -28
- package/src/components/OcMediaThumb.vue +38 -38
- package/src/components/OcMessage.vue +91 -91
- package/src/components/OcMessageList.vue +43 -43
- package/src/components/OcMessageThread.vue +155 -155
- package/src/components/OcModal.vue +42 -42
- package/src/components/OcNavmenu.vue +18 -18
- package/src/components/OcNavmenuList.vue +38 -38
- package/src/components/OcNextTeaser.vue +25 -25
- package/src/components/OcNumberInput.vue +47 -47
- package/src/components/OcOtpInput.vue +34 -34
- package/src/components/OcPageHeader.vue +34 -34
- package/src/components/OcPagination.vue +33 -33
- package/src/components/OcPasswordInput.vue +27 -27
- package/src/components/OcPasswordRequirements.vue +28 -28
- package/src/components/OcPhoneInput.vue +72 -72
- package/src/components/OcPopover.vue +25 -25
- package/src/components/OcPriceCard.vue +69 -69
- package/src/components/OcProductMark.vue +31 -31
- package/src/components/OcProgress.vue +21 -21
- package/src/components/OcProjectCard.vue +36 -36
- package/src/components/OcProse.vue +57 -57
- package/src/components/OcRadio.vue +40 -40
- package/src/components/OcRadioGroup.vue +32 -32
- package/src/components/OcRecoveryCodes.vue +55 -55
- package/src/components/OcScrollArea.vue +15 -15
- package/src/components/OcSegmented.vue +28 -28
- package/src/components/OcSelect.vue +21 -21
- package/src/components/OcSeparator.vue +18 -18
- package/src/components/OcSeverityCard.vue +46 -46
- package/src/components/OcSheet.vue +49 -49
- package/src/components/OcSidebarCollapsible.vue +19 -19
- package/src/components/OcSidebarGroup.vue +11 -11
- package/src/components/OcSidebarMenu.vue +7 -7
- package/src/components/OcSidebarMenuButton.vue +31 -31
- package/src/components/OcSidebarSeparator.vue +7 -7
- package/src/components/OcSkeleton.vue +8 -8
- package/src/components/OcSlider.vue +27 -27
- package/src/components/OcSortableRow.vue +40 -40
- package/src/components/OcSpinner.vue +13 -13
- package/src/components/OcStackCard.vue +24 -24
- package/src/components/OcStackList.vue +31 -31
- package/src/components/OcStatCard.vue +39 -39
- package/src/components/OcStepper.vue +34 -34
- package/src/components/OcSwitch.vue +15 -15
- package/src/components/OcTable.vue +115 -115
- package/src/components/OcTableActions.vue +26 -26
- package/src/components/OcTableCompact.vue +46 -46
- package/src/components/OcTabs.vue +37 -37
- package/src/components/OcTagInput.vue +50 -50
- package/src/components/OcTerminalLog.vue +50 -50
- package/src/components/OcTextarea.vue +9 -9
- package/src/components/OcThemeSwitch.vue +31 -31
- package/src/components/OcTimeInput.vue +50 -50
- package/src/components/OcTimeline.vue +36 -36
- package/src/components/OcToast.vue +31 -31
- package/src/components/OcToastContainer.vue +21 -21
- package/src/components/OcToggle.vue +63 -63
- package/src/components/OcToggleField.vue +28 -28
- package/src/components/OcToggleGroup.vue +40 -40
- package/src/components/OcTooltip.vue +8 -8
- package/src/components/OcTopProgress.vue +54 -54
- package/src/components/OcTree.vue +70 -70
- package/src/components/OcUpgradeBanner.vue +42 -42
- package/src/components/OcUser.vue +25 -25
- package/src/components/OcVersionBanner.vue +34 -34
- package/src/composables/chartLine.ts +28 -28
- package/src/composables/keys.ts +22 -22
- package/src/composables/useAttrsSplit.ts +16 -16
- package/src/composables/useDialog.ts +21 -21
- package/src/composables/useToast.ts +66 -66
- package/src/composables/useUid.ts +12 -12
|
@@ -1,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)">−</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)">−</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>
|