@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,50 +1,50 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcTerminalLog.vue | Map: props -> frame -> bar (dots, title, status) -> body (lines + prompt) -> auto-scroll -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Dark console window (same markup as snippets/terminal-log.html).
|
|
5
|
-
* Props: lines ({ level: info|success|warn|error|debug, message, time?, label? }[]), title, status ("Live" + pulsing dot),
|
|
6
|
-
* prompt (text, or true for an empty prompt with cursor), height (max body height), autoScroll (default true), label.
|
|
7
|
-
* Classes: .oc-terminal-log, __bar, __title, __status, __body, __line(--info/--success/--warn/--error/--debug), __time, __level, __msg, __prompt
|
|
8
|
-
*/
|
|
9
|
-
import { nextTick, ref, watch } from 'vue'
|
|
10
|
-
|
|
11
|
-
const props = withDefaults(
|
|
12
|
-
defineProps<{
|
|
13
|
-
lines: { level: 'info' | 'success' | 'warn' | 'error' | 'debug'; message: string; time?: string; label?: string }[]
|
|
14
|
-
title?: string
|
|
15
|
-
status?: string
|
|
16
|
-
prompt?: string | boolean
|
|
17
|
-
height?: string
|
|
18
|
-
autoScroll?: boolean
|
|
19
|
-
label?: string
|
|
20
|
-
}>(),
|
|
21
|
-
{ autoScroll: true, label: 'Log output', prompt: false },
|
|
22
|
-
)
|
|
23
|
-
const body = ref<HTMLDivElement | null>(null)
|
|
24
|
-
// Keep the newest line in view.
|
|
25
|
-
watch(
|
|
26
|
-
() => props.lines.length,
|
|
27
|
-
async () => {
|
|
28
|
-
await nextTick()
|
|
29
|
-
if (props.autoScroll && body.value) body.value.scrollTop = body.value.scrollHeight
|
|
30
|
-
},
|
|
31
|
-
)
|
|
32
|
-
</script>
|
|
33
|
-
|
|
34
|
-
<template>
|
|
35
|
-
<div class="oc-terminal-log" :style="height ? { '--oc-terminal-log-height': height } : undefined">
|
|
36
|
-
<div class="oc-terminal-log__bar">
|
|
37
|
-
<i /><i /><i />
|
|
38
|
-
<span v-if="title" class="oc-terminal-log__title">{{ title }}</span>
|
|
39
|
-
<span v-if="status" class="oc-terminal-log__status">{{ status }}</span>
|
|
40
|
-
</div>
|
|
41
|
-
<div ref="body" class="oc-terminal-log__body" role="log" aria-live="polite" :aria-label="label" tabindex="0">
|
|
42
|
-
<p v-for="(l, i) in lines" :key="i" :class="['oc-terminal-log__line', `oc-terminal-log__line--${l.level}`]">
|
|
43
|
-
<time v-if="l.time" class="oc-terminal-log__time">{{ l.time }}</time>
|
|
44
|
-
<span class="oc-terminal-log__level">{{ l.label ?? (l.level === 'success' ? 'ok' : l.level) }}</span>
|
|
45
|
-
<span class="oc-terminal-log__msg">{{ l.message }}</span>
|
|
46
|
-
</p>
|
|
47
|
-
<p v-if="prompt !== false" class="oc-terminal-log__prompt">{{ prompt === true ? '' : prompt }}</p>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcTerminalLog.vue | Map: props -> frame -> bar (dots, title, status) -> body (lines + prompt) -> auto-scroll -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Dark console window (same markup as snippets/terminal-log.html).
|
|
5
|
+
* Props: lines ({ level: info|success|warn|error|debug, message, time?, label? }[]), title, status ("Live" + pulsing dot),
|
|
6
|
+
* prompt (text, or true for an empty prompt with cursor), height (max body height), autoScroll (default true), label.
|
|
7
|
+
* Classes: .oc-terminal-log, __bar, __title, __status, __body, __line(--info/--success/--warn/--error/--debug), __time, __level, __msg, __prompt
|
|
8
|
+
*/
|
|
9
|
+
import { nextTick, ref, watch } from 'vue'
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(
|
|
12
|
+
defineProps<{
|
|
13
|
+
lines: { level: 'info' | 'success' | 'warn' | 'error' | 'debug'; message: string; time?: string; label?: string }[]
|
|
14
|
+
title?: string
|
|
15
|
+
status?: string
|
|
16
|
+
prompt?: string | boolean
|
|
17
|
+
height?: string
|
|
18
|
+
autoScroll?: boolean
|
|
19
|
+
label?: string
|
|
20
|
+
}>(),
|
|
21
|
+
{ autoScroll: true, label: 'Log output', prompt: false },
|
|
22
|
+
)
|
|
23
|
+
const body = ref<HTMLDivElement | null>(null)
|
|
24
|
+
// Keep the newest line in view.
|
|
25
|
+
watch(
|
|
26
|
+
() => props.lines.length,
|
|
27
|
+
async () => {
|
|
28
|
+
await nextTick()
|
|
29
|
+
if (props.autoScroll && body.value) body.value.scrollTop = body.value.scrollHeight
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div class="oc-terminal-log" :style="height ? { '--oc-terminal-log-height': height } : undefined">
|
|
36
|
+
<div class="oc-terminal-log__bar">
|
|
37
|
+
<i /><i /><i />
|
|
38
|
+
<span v-if="title" class="oc-terminal-log__title">{{ title }}</span>
|
|
39
|
+
<span v-if="status" class="oc-terminal-log__status">{{ status }}</span>
|
|
40
|
+
</div>
|
|
41
|
+
<div ref="body" class="oc-terminal-log__body" role="log" aria-live="polite" :aria-label="label" tabindex="0">
|
|
42
|
+
<p v-for="(l, i) in lines" :key="i" :class="['oc-terminal-log__line', `oc-terminal-log__line--${l.level}`]">
|
|
43
|
+
<time v-if="l.time" class="oc-terminal-log__time">{{ l.time }}</time>
|
|
44
|
+
<span class="oc-terminal-log__level">{{ l.label ?? (l.level === 'success' ? 'ok' : l.level) }}</span>
|
|
45
|
+
<span class="oc-terminal-log__msg">{{ l.message }}</span>
|
|
46
|
+
</p>
|
|
47
|
+
<p v-if="prompt !== false" class="oc-terminal-log__prompt">{{ prompt === true ? '' : prompt }}</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Multi-line input (uses .oc-input). v-model. */
|
|
3
|
-
defineProps<{ error?: boolean }>()
|
|
4
|
-
const model = defineModel<string>()
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<template>
|
|
8
|
-
<textarea v-model="model" :class="['oc-input', error && 'oc-input--error']" :aria-invalid="error ? 'true' : undefined"></textarea>
|
|
9
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Multi-line input (uses .oc-input). v-model. */
|
|
3
|
+
defineProps<{ error?: boolean }>()
|
|
4
|
+
const model = defineModel<string>()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<textarea v-model="model" :class="['oc-input', error && 'oc-input--error']" :aria-invalid="error ? 'true' : undefined"></textarea>
|
|
9
|
+
</template>
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* System / light / dark switch (CSS only: color-scheme + :has()). v-model: 'system' | 'light' | 'dark'.
|
|
4
|
-
* `options` limits the choices; `as="div"` renders a role="radiogroup" div instead of a fieldset.
|
|
5
|
-
*/
|
|
6
|
-
import { useUid } from '../composables/useUid'
|
|
7
|
-
|
|
8
|
-
type Theme = 'system' | 'light' | 'dark'
|
|
9
|
-
const labels: Record<Theme, string> = { system: 'System', light: 'Light', dark: 'Dark' }
|
|
10
|
-
|
|
11
|
-
withDefaults(
|
|
12
|
-
defineProps<{
|
|
13
|
-
options?: Theme[]
|
|
14
|
-
as?: 'fieldset' | 'div'
|
|
15
|
-
label?: string
|
|
16
|
-
name?: string
|
|
17
|
-
}>(),
|
|
18
|
-
{ options: () => ['system', 'light', 'dark'], as: 'fieldset', label: 'Color theme' },
|
|
19
|
-
)
|
|
20
|
-
const model = defineModel<Theme>({ default: 'system' })
|
|
21
|
-
const uid = useUid('oc-theme')
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<template>
|
|
25
|
-
<component :is="as" class="oc-theme-switch" :role="as === 'div' ? 'radiogroup' : undefined" :aria-label="label">
|
|
26
|
-
<label v-for="o in options" :key="o">
|
|
27
|
-
<input v-model="model" type="radio" :name="name ?? uid" :value="o" />
|
|
28
|
-
{{ labels[o] }}
|
|
29
|
-
</label>
|
|
30
|
-
</component>
|
|
31
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* System / light / dark switch (CSS only: color-scheme + :has()). v-model: 'system' | 'light' | 'dark'.
|
|
4
|
+
* `options` limits the choices; `as="div"` renders a role="radiogroup" div instead of a fieldset.
|
|
5
|
+
*/
|
|
6
|
+
import { useUid } from '../composables/useUid'
|
|
7
|
+
|
|
8
|
+
type Theme = 'system' | 'light' | 'dark'
|
|
9
|
+
const labels: Record<Theme, string> = { system: 'System', light: 'Light', dark: 'Dark' }
|
|
10
|
+
|
|
11
|
+
withDefaults(
|
|
12
|
+
defineProps<{
|
|
13
|
+
options?: Theme[]
|
|
14
|
+
as?: 'fieldset' | 'div'
|
|
15
|
+
label?: string
|
|
16
|
+
name?: string
|
|
17
|
+
}>(),
|
|
18
|
+
{ options: () => ['system', 'light', 'dark'], as: 'fieldset', label: 'Color theme' },
|
|
19
|
+
)
|
|
20
|
+
const model = defineModel<Theme>({ default: 'system' })
|
|
21
|
+
const uid = useUid('oc-theme')
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<component :is="as" class="oc-theme-switch" :role="as === 'div' ? 'radiogroup' : undefined" :aria-label="label">
|
|
26
|
+
<label v-for="o in options" :key="o">
|
|
27
|
+
<input v-model="model" type="radio" :name="name ?? uid" :value="o" />
|
|
28
|
+
{{ labels[o] }}
|
|
29
|
+
</label>
|
|
30
|
+
</component>
|
|
31
|
+
</template>
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcTimeInput.vue | Map: props + model -> format helpers -> template (text field + datalist) -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* 24h time field (HH:mm). v-model: "HH:mm" or "". `slots` fills a datalist with suggestions.
|
|
5
|
-
* Same markup as packages/ui/snippets/time-input.html. JS only adds the colon ("0930" -> "09:30").
|
|
6
|
-
* All attributes go to the input.
|
|
7
|
-
*/
|
|
8
|
-
import { useUid } from '../composables/useUid'
|
|
9
|
-
|
|
10
|
-
defineOptions({ inheritAttrs: false })
|
|
11
|
-
defineProps<{ slots?: string[]; error?: boolean; placeholder?: string }>()
|
|
12
|
-
const model = defineModel<string>({ default: '' })
|
|
13
|
-
const listId = useUid('oc-time')
|
|
14
|
-
|
|
15
|
-
/** Up to 4 digits with a colon after the first two. A picked datalist option is kept as is. */
|
|
16
|
-
function onInput(e: Event) {
|
|
17
|
-
const input = e.target as HTMLInputElement
|
|
18
|
-
const v = input.value
|
|
19
|
-
const digits = v.replace(/\D/g, '').slice(0, 4)
|
|
20
|
-
model.value = /^\d{2}:\d{2}$/.test(v) ? v : digits.length <= 2 ? digits : `${digits.slice(0, 2)}:${digits.slice(2)}`
|
|
21
|
-
input.value = model.value
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** On blur: "9" -> "09:00", "930" -> "09:30". */
|
|
25
|
-
function onBlur() {
|
|
26
|
-
const d = model.value.replace(/\D/g, '')
|
|
27
|
-
if (d.length === 1 || d.length === 2) model.value = `${d.padStart(2, '0')}:00`
|
|
28
|
-
else if (d.length === 3) model.value = `0${d[0]}:${d.slice(1)}`
|
|
29
|
-
}
|
|
30
|
-
</script>
|
|
31
|
-
|
|
32
|
-
<template>
|
|
33
|
-
<input
|
|
34
|
-
v-bind="$attrs"
|
|
35
|
-
class="oc-input oc-time-input"
|
|
36
|
-
type="text"
|
|
37
|
-
inputmode="numeric"
|
|
38
|
-
maxlength="5"
|
|
39
|
-
pattern="([01][0-9]|2[0-3]):[0-5][0-9]"
|
|
40
|
-
:placeholder="placeholder ?? 'hh:mm'"
|
|
41
|
-
:list="slots?.length ? listId : undefined"
|
|
42
|
-
:aria-invalid="error ? 'true' : undefined"
|
|
43
|
-
:value="model"
|
|
44
|
-
@input="onInput"
|
|
45
|
-
@blur="onBlur"
|
|
46
|
-
/>
|
|
47
|
-
<datalist v-if="slots?.length" :id="listId">
|
|
48
|
-
<option v-for="s in slots" :key="s" :value="s"></option>
|
|
49
|
-
</datalist>
|
|
50
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcTimeInput.vue | Map: props + model -> format helpers -> template (text field + datalist) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* 24h time field (HH:mm). v-model: "HH:mm" or "". `slots` fills a datalist with suggestions.
|
|
5
|
+
* Same markup as packages/ui/snippets/time-input.html. JS only adds the colon ("0930" -> "09:30").
|
|
6
|
+
* All attributes go to the input.
|
|
7
|
+
*/
|
|
8
|
+
import { useUid } from '../composables/useUid'
|
|
9
|
+
|
|
10
|
+
defineOptions({ inheritAttrs: false })
|
|
11
|
+
defineProps<{ slots?: string[]; error?: boolean; placeholder?: string }>()
|
|
12
|
+
const model = defineModel<string>({ default: '' })
|
|
13
|
+
const listId = useUid('oc-time')
|
|
14
|
+
|
|
15
|
+
/** Up to 4 digits with a colon after the first two. A picked datalist option is kept as is. */
|
|
16
|
+
function onInput(e: Event) {
|
|
17
|
+
const input = e.target as HTMLInputElement
|
|
18
|
+
const v = input.value
|
|
19
|
+
const digits = v.replace(/\D/g, '').slice(0, 4)
|
|
20
|
+
model.value = /^\d{2}:\d{2}$/.test(v) ? v : digits.length <= 2 ? digits : `${digits.slice(0, 2)}:${digits.slice(2)}`
|
|
21
|
+
input.value = model.value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** On blur: "9" -> "09:00", "930" -> "09:30". */
|
|
25
|
+
function onBlur() {
|
|
26
|
+
const d = model.value.replace(/\D/g, '')
|
|
27
|
+
if (d.length === 1 || d.length === 2) model.value = `${d.padStart(2, '0')}:00`
|
|
28
|
+
else if (d.length === 3) model.value = `0${d[0]}:${d.slice(1)}`
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<input
|
|
34
|
+
v-bind="$attrs"
|
|
35
|
+
class="oc-input oc-time-input"
|
|
36
|
+
type="text"
|
|
37
|
+
inputmode="numeric"
|
|
38
|
+
maxlength="5"
|
|
39
|
+
pattern="([01][0-9]|2[0-3]):[0-5][0-9]"
|
|
40
|
+
:placeholder="placeholder ?? 'hh:mm'"
|
|
41
|
+
:list="slots?.length ? listId : undefined"
|
|
42
|
+
:aria-invalid="error ? 'true' : undefined"
|
|
43
|
+
:value="model"
|
|
44
|
+
@input="onInput"
|
|
45
|
+
@blur="onBlur"
|
|
46
|
+
/>
|
|
47
|
+
<datalist v-if="slots?.length" :id="listId">
|
|
48
|
+
<option v-for="s in slots" :key="s" :value="s"></option>
|
|
49
|
+
</datalist>
|
|
50
|
+
</template>
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcTimeline.vue | Map: props (items, size, accent) -> ol.oc-timeline -> li.oc-timeline__item -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Vertical timeline. Pass `items`, or put your own li.oc-timeline__item elements in the default slot.
|
|
5
|
-
* Classes: .oc-timeline (--lg, --accent), .oc-timeline__item (--current), __date, __title, __text
|
|
6
|
-
*/
|
|
7
|
-
export interface TimelineEntry {
|
|
8
|
-
date: string
|
|
9
|
-
/** Renders a <time datetime> when set. */
|
|
10
|
-
dateTime?: string
|
|
11
|
-
title?: string
|
|
12
|
-
text?: string
|
|
13
|
-
current?: boolean
|
|
14
|
-
}
|
|
15
|
-
withDefaults(
|
|
16
|
-
defineProps<{
|
|
17
|
-
items?: TimelineEntry[]
|
|
18
|
-
size?: 'default' | 'lg'
|
|
19
|
-
accent?: boolean
|
|
20
|
-
}>(),
|
|
21
|
-
{ items: () => [], size: 'default', accent: false },
|
|
22
|
-
)
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
<template>
|
|
26
|
-
<ol :class="['oc-timeline', size === 'lg' && 'oc-timeline--lg', accent && 'oc-timeline--accent']">
|
|
27
|
-
<slot>
|
|
28
|
-
<li v-for="(item, i) in items" :key="i" :class="['oc-timeline__item', item.current && 'oc-timeline__item--current']">
|
|
29
|
-
<time v-if="item.dateTime" class="oc-timeline__date" :datetime="item.dateTime">{{ item.date }}</time>
|
|
30
|
-
<span v-else class="oc-timeline__date">{{ item.date }}</span>
|
|
31
|
-
<h3 v-if="item.title" class="oc-timeline__title">{{ item.title }}</h3>
|
|
32
|
-
<p v-if="item.text" class="oc-timeline__text">{{ item.text }}</p>
|
|
33
|
-
</li>
|
|
34
|
-
</slot>
|
|
35
|
-
</ol>
|
|
36
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcTimeline.vue | Map: props (items, size, accent) -> ol.oc-timeline -> li.oc-timeline__item -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Vertical timeline. Pass `items`, or put your own li.oc-timeline__item elements in the default slot.
|
|
5
|
+
* Classes: .oc-timeline (--lg, --accent), .oc-timeline__item (--current), __date, __title, __text
|
|
6
|
+
*/
|
|
7
|
+
export interface TimelineEntry {
|
|
8
|
+
date: string
|
|
9
|
+
/** Renders a <time datetime> when set. */
|
|
10
|
+
dateTime?: string
|
|
11
|
+
title?: string
|
|
12
|
+
text?: string
|
|
13
|
+
current?: boolean
|
|
14
|
+
}
|
|
15
|
+
withDefaults(
|
|
16
|
+
defineProps<{
|
|
17
|
+
items?: TimelineEntry[]
|
|
18
|
+
size?: 'default' | 'lg'
|
|
19
|
+
accent?: boolean
|
|
20
|
+
}>(),
|
|
21
|
+
{ items: () => [], size: 'default', accent: false },
|
|
22
|
+
)
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<ol :class="['oc-timeline', size === 'lg' && 'oc-timeline--lg', accent && 'oc-timeline--accent']">
|
|
27
|
+
<slot>
|
|
28
|
+
<li v-for="(item, i) in items" :key="i" :class="['oc-timeline__item', item.current && 'oc-timeline__item--current']">
|
|
29
|
+
<time v-if="item.dateTime" class="oc-timeline__date" :datetime="item.dateTime">{{ item.date }}</time>
|
|
30
|
+
<span v-else class="oc-timeline__date">{{ item.date }}</span>
|
|
31
|
+
<h3 v-if="item.title" class="oc-timeline__title">{{ item.title }}</h3>
|
|
32
|
+
<p v-if="item.text" class="oc-timeline__text">{{ item.text }}</p>
|
|
33
|
+
</li>
|
|
34
|
+
</slot>
|
|
35
|
+
</ol>
|
|
36
|
+
</template>
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* One notification. Emits `dismiss`. role="alert" for errors, role="status" otherwise.
|
|
4
|
-
* Slots: icon, title, default (description). Classes: .oc-toast--success/error/warning/info, -icon, -body,
|
|
5
|
-
* -title, -description, -close
|
|
6
|
-
*/
|
|
7
|
-
const glyphs = { success: '✓', error: '!', warning: '!', info: 'i' } as const
|
|
8
|
-
|
|
9
|
-
withDefaults(
|
|
10
|
-
defineProps<{
|
|
11
|
-
variant?: 'success' | 'error' | 'warning' | 'info'
|
|
12
|
-
title?: string
|
|
13
|
-
description?: string
|
|
14
|
-
dismissible?: boolean
|
|
15
|
-
dismissLabel?: string
|
|
16
|
-
}>(),
|
|
17
|
-
{ variant: 'info', dismissible: true, dismissLabel: 'Dismiss' },
|
|
18
|
-
)
|
|
19
|
-
defineEmits<{ dismiss: [] }>()
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<template>
|
|
23
|
-
<div :class="['oc-toast', `oc-toast--${variant}`]" :role="variant === 'error' ? 'alert' : 'status'">
|
|
24
|
-
<span class="oc-toast-icon" aria-hidden="true"><slot name="icon">{{ glyphs[variant] }}</slot></span>
|
|
25
|
-
<div class="oc-toast-body">
|
|
26
|
-
<p class="oc-toast-title"><slot name="title">{{ title }}</slot></p>
|
|
27
|
-
<p v-if="description || $slots.default" class="oc-toast-description"><slot>{{ description }}</slot></p>
|
|
28
|
-
</div>
|
|
29
|
-
<button v-if="dismissible" class="oc-toast-close" type="button" :aria-label="dismissLabel" @click="$emit('dismiss')">×</button>
|
|
30
|
-
</div>
|
|
31
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* One notification. Emits `dismiss`. role="alert" for errors, role="status" otherwise.
|
|
4
|
+
* Slots: icon, title, default (description). Classes: .oc-toast--success/error/warning/info, -icon, -body,
|
|
5
|
+
* -title, -description, -close
|
|
6
|
+
*/
|
|
7
|
+
const glyphs = { success: '✓', error: '!', warning: '!', info: 'i' } as const
|
|
8
|
+
|
|
9
|
+
withDefaults(
|
|
10
|
+
defineProps<{
|
|
11
|
+
variant?: 'success' | 'error' | 'warning' | 'info'
|
|
12
|
+
title?: string
|
|
13
|
+
description?: string
|
|
14
|
+
dismissible?: boolean
|
|
15
|
+
dismissLabel?: string
|
|
16
|
+
}>(),
|
|
17
|
+
{ variant: 'info', dismissible: true, dismissLabel: 'Dismiss' },
|
|
18
|
+
)
|
|
19
|
+
defineEmits<{ dismiss: [] }>()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<div :class="['oc-toast', `oc-toast--${variant}`]" :role="variant === 'error' ? 'alert' : 'status'">
|
|
24
|
+
<span class="oc-toast-icon" aria-hidden="true"><slot name="icon">{{ glyphs[variant] }}</slot></span>
|
|
25
|
+
<div class="oc-toast-body">
|
|
26
|
+
<p class="oc-toast-title"><slot name="title">{{ title }}</slot></p>
|
|
27
|
+
<p v-if="description || $slots.default" class="oc-toast-description"><slot>{{ description }}</slot></p>
|
|
28
|
+
</div>
|
|
29
|
+
<button v-if="dismissible" class="oc-toast-close" type="button" :aria-label="dismissLabel" @click="$emit('dismiss')">×</button>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Fixed stack that renders the useToast() queue. Mount once near the app root. Classes: .oc-toast-container(--top/--static) */
|
|
3
|
-
import { useToast } from '../composables/useToast'
|
|
4
|
-
import OcToast from './OcToast.vue'
|
|
5
|
-
|
|
6
|
-
withDefaults(defineProps<{ position?: 'bottom' | 'top'; static?: boolean }>(), { position: 'bottom' })
|
|
7
|
-
const { toasts, dismiss } = useToast()
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<template>
|
|
11
|
-
<div :class="['oc-toast-container', position === 'top' && 'oc-toast-container--top', static && 'oc-toast-container--static']">
|
|
12
|
-
<OcToast
|
|
13
|
-
v-for="t in toasts"
|
|
14
|
-
:key="t.id"
|
|
15
|
-
:variant="t.variant"
|
|
16
|
-
:title="t.title"
|
|
17
|
-
:description="t.description"
|
|
18
|
-
@dismiss="dismiss(t.id)"
|
|
19
|
-
/>
|
|
20
|
-
</div>
|
|
21
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Fixed stack that renders the useToast() queue. Mount once near the app root. Classes: .oc-toast-container(--top/--static) */
|
|
3
|
+
import { useToast } from '../composables/useToast'
|
|
4
|
+
import OcToast from './OcToast.vue'
|
|
5
|
+
|
|
6
|
+
withDefaults(defineProps<{ position?: 'bottom' | 'top'; static?: boolean }>(), { position: 'bottom' })
|
|
7
|
+
const { toasts, dismiss } = useToast()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div :class="['oc-toast-container', position === 'top' && 'oc-toast-container--top', static && 'oc-toast-container--static']">
|
|
12
|
+
<OcToast
|
|
13
|
+
v-for="t in toasts"
|
|
14
|
+
:key="t.id"
|
|
15
|
+
:variant="t.variant"
|
|
16
|
+
:title="t.title"
|
|
17
|
+
:description="t.description"
|
|
18
|
+
@dismiss="dismiss(t.id)"
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Toggle. Inside OcToggleGroup (give it a `value`) it is a label around a radio/checkbox bound to the group's v-model.
|
|
4
|
-
* Standalone: v-model boolean on a label + checkbox, or `button` for a <button aria-pressed>.
|
|
5
|
-
* Pass an aria-label attribute for icon-only toggles (it lands on the input/button).
|
|
6
|
-
* Classes: .oc-toggle(--outline/--sm/--lg/--icon)
|
|
7
|
-
*/
|
|
8
|
-
import { computed, inject } from 'vue'
|
|
9
|
-
import { useAttrsSplit } from '../composables/useAttrsSplit'
|
|
10
|
-
import { toggleGroupKey } from '../composables/keys'
|
|
11
|
-
|
|
12
|
-
defineOptions({ inheritAttrs: false })
|
|
13
|
-
const props = defineProps<{
|
|
14
|
-
value?: string
|
|
15
|
-
variant?: 'default' | 'outline'
|
|
16
|
-
size?: 'sm' | 'md' | 'lg'
|
|
17
|
-
icon?: boolean
|
|
18
|
-
button?: boolean
|
|
19
|
-
disabled?: boolean
|
|
20
|
-
}>()
|
|
21
|
-
const pressed = defineModel<boolean>({ default: false })
|
|
22
|
-
const group = inject(toggleGroupKey, null)
|
|
23
|
-
const { root, control } = useAttrsSplit()
|
|
24
|
-
|
|
25
|
-
const classes = computed(() => [
|
|
26
|
-
'oc-toggle',
|
|
27
|
-
props.variant === 'outline' && 'oc-toggle--outline',
|
|
28
|
-
props.size && props.size !== 'md' && `oc-toggle--${props.size}`,
|
|
29
|
-
props.icon && 'oc-toggle--icon',
|
|
30
|
-
])
|
|
31
|
-
const inGroup = computed(() => !!group && props.value !== undefined)
|
|
32
|
-
const isOn = computed(() => (inGroup.value ? group!.values.value.includes(props.value!) : pressed.value))
|
|
33
|
-
function onChange() {
|
|
34
|
-
if (inGroup.value) group!.toggle(props.value!)
|
|
35
|
-
else pressed.value = !pressed.value
|
|
36
|
-
}
|
|
37
|
-
</script>
|
|
38
|
-
|
|
39
|
-
<template>
|
|
40
|
-
<button
|
|
41
|
-
v-if="button && !inGroup"
|
|
42
|
-
v-bind="{ ...root, ...control }"
|
|
43
|
-
type="button"
|
|
44
|
-
:class="classes"
|
|
45
|
-
:aria-pressed="pressed ? 'true' : 'false'"
|
|
46
|
-
:disabled="disabled"
|
|
47
|
-
@click="pressed = !pressed"
|
|
48
|
-
>
|
|
49
|
-
<slot />
|
|
50
|
-
</button>
|
|
51
|
-
<label v-else v-bind="root" :class="classes">
|
|
52
|
-
<input
|
|
53
|
-
v-bind="control"
|
|
54
|
-
:type="inGroup && group!.type.value === 'single' ? 'radio' : 'checkbox'"
|
|
55
|
-
:name="inGroup ? group!.name.value : (control.name as string | undefined)"
|
|
56
|
-
:value="value"
|
|
57
|
-
:checked="isOn"
|
|
58
|
-
:disabled="disabled"
|
|
59
|
-
@change="onChange"
|
|
60
|
-
/>
|
|
61
|
-
<slot />
|
|
62
|
-
</label>
|
|
63
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Toggle. Inside OcToggleGroup (give it a `value`) it is a label around a radio/checkbox bound to the group's v-model.
|
|
4
|
+
* Standalone: v-model boolean on a label + checkbox, or `button` for a <button aria-pressed>.
|
|
5
|
+
* Pass an aria-label attribute for icon-only toggles (it lands on the input/button).
|
|
6
|
+
* Classes: .oc-toggle(--outline/--sm/--lg/--icon)
|
|
7
|
+
*/
|
|
8
|
+
import { computed, inject } from 'vue'
|
|
9
|
+
import { useAttrsSplit } from '../composables/useAttrsSplit'
|
|
10
|
+
import { toggleGroupKey } from '../composables/keys'
|
|
11
|
+
|
|
12
|
+
defineOptions({ inheritAttrs: false })
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
value?: string
|
|
15
|
+
variant?: 'default' | 'outline'
|
|
16
|
+
size?: 'sm' | 'md' | 'lg'
|
|
17
|
+
icon?: boolean
|
|
18
|
+
button?: boolean
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
}>()
|
|
21
|
+
const pressed = defineModel<boolean>({ default: false })
|
|
22
|
+
const group = inject(toggleGroupKey, null)
|
|
23
|
+
const { root, control } = useAttrsSplit()
|
|
24
|
+
|
|
25
|
+
const classes = computed(() => [
|
|
26
|
+
'oc-toggle',
|
|
27
|
+
props.variant === 'outline' && 'oc-toggle--outline',
|
|
28
|
+
props.size && props.size !== 'md' && `oc-toggle--${props.size}`,
|
|
29
|
+
props.icon && 'oc-toggle--icon',
|
|
30
|
+
])
|
|
31
|
+
const inGroup = computed(() => !!group && props.value !== undefined)
|
|
32
|
+
const isOn = computed(() => (inGroup.value ? group!.values.value.includes(props.value!) : pressed.value))
|
|
33
|
+
function onChange() {
|
|
34
|
+
if (inGroup.value) group!.toggle(props.value!)
|
|
35
|
+
else pressed.value = !pressed.value
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<button
|
|
41
|
+
v-if="button && !inGroup"
|
|
42
|
+
v-bind="{ ...root, ...control }"
|
|
43
|
+
type="button"
|
|
44
|
+
:class="classes"
|
|
45
|
+
:aria-pressed="pressed ? 'true' : 'false'"
|
|
46
|
+
:disabled="disabled"
|
|
47
|
+
@click="pressed = !pressed"
|
|
48
|
+
>
|
|
49
|
+
<slot />
|
|
50
|
+
</button>
|
|
51
|
+
<label v-else v-bind="root" :class="classes">
|
|
52
|
+
<input
|
|
53
|
+
v-bind="control"
|
|
54
|
+
:type="inGroup && group!.type.value === 'single' ? 'radio' : 'checkbox'"
|
|
55
|
+
:name="inGroup ? group!.name.value : (control.name as string | undefined)"
|
|
56
|
+
:value="value"
|
|
57
|
+
:checked="isOn"
|
|
58
|
+
:disabled="disabled"
|
|
59
|
+
@change="onChange"
|
|
60
|
+
/>
|
|
61
|
+
<slot />
|
|
62
|
+
</label>
|
|
63
|
+
</template>
|