@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,19 +1,19 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Card-style details/summary section. v-model:open. Slots: icon, title, default (content). Classes: --dense */
|
|
3
|
-
defineProps<{ title?: string; dense?: boolean }>()
|
|
4
|
-
const open = defineModel<boolean>('open', { default: false })
|
|
5
|
-
|
|
6
|
-
function onToggle(e: Event) {
|
|
7
|
-
open.value = (e.target as HTMLDetailsElement).open
|
|
8
|
-
}
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<template>
|
|
12
|
-
<details :class="['oc-collapsible', dense && 'oc-collapsible--dense']" :open="open" @toggle="onToggle">
|
|
13
|
-
<summary>
|
|
14
|
-
<slot name="icon" />
|
|
15
|
-
<span class="oc-collapsible-title"><slot name="title">{{ title }}</slot></span>
|
|
16
|
-
</summary>
|
|
17
|
-
<div class="oc-collapsible-content"><slot /></div>
|
|
18
|
-
</details>
|
|
19
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Card-style details/summary section. v-model:open. Slots: icon, title, default (content). Classes: --dense */
|
|
3
|
+
defineProps<{ title?: string; dense?: boolean }>()
|
|
4
|
+
const open = defineModel<boolean>('open', { default: false })
|
|
5
|
+
|
|
6
|
+
function onToggle(e: Event) {
|
|
7
|
+
open.value = (e.target as HTMLDetailsElement).open
|
|
8
|
+
}
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<details :class="['oc-collapsible', dense && 'oc-collapsible--dense']" :open="open" @toggle="onToggle">
|
|
13
|
+
<summary>
|
|
14
|
+
<slot name="icon" />
|
|
15
|
+
<span class="oc-collapsible-title"><slot name="title">{{ title }}</slot></span>
|
|
16
|
+
</summary>
|
|
17
|
+
<div class="oc-collapsible-content"><slot /></div>
|
|
18
|
+
</details>
|
|
19
|
+
</template>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/** Native color picker next to a hex text input, kept in sync. v-model: color string (#rrggbb). */
|
|
3
|
-
withDefaults(defineProps<{ pickerLabel?: string; valueLabel?: string }>(), {
|
|
4
|
-
pickerLabel: 'Pick a color',
|
|
5
|
-
valueLabel: 'Color value',
|
|
6
|
-
})
|
|
7
|
-
const model = defineModel<string>({ default: '#000000' })
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<template>
|
|
11
|
-
<div class="oc-color-field">
|
|
12
|
-
<input v-model="model" type="color" :aria-label="pickerLabel" />
|
|
13
|
-
<input v-model="model" class="oc-input" type="text" :aria-label="valueLabel" />
|
|
14
|
-
</div>
|
|
15
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Native color picker next to a hex text input, kept in sync. v-model: color string (#rrggbb). */
|
|
3
|
+
withDefaults(defineProps<{ pickerLabel?: string; valueLabel?: string }>(), {
|
|
4
|
+
pickerLabel: 'Pick a color',
|
|
5
|
+
valueLabel: 'Color value',
|
|
6
|
+
})
|
|
7
|
+
const model = defineModel<string>({ default: '#000000' })
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div class="oc-color-field">
|
|
12
|
+
<input v-model="model" type="color" :aria-label="pickerLabel" />
|
|
13
|
+
<input v-model="model" class="oc-input" type="text" :aria-label="valueLabel" />
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcColorSelect.vue | Map: types + props -> template (fieldset radiogroup -> label per color) -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/** Pick one color from swatches. v-model = selected value. Classes: .oc-color-select(--list/--sm), -option, -swatch, -name */
|
|
4
|
-
import { useUid } from '../composables/useUid'
|
|
5
|
-
|
|
6
|
-
export interface ColorSelectItem {
|
|
7
|
-
value: string
|
|
8
|
-
label: string
|
|
9
|
-
/** CSS color, use Omnia tokens: "var(--color-red-70)". */
|
|
10
|
-
color: string
|
|
11
|
-
disabled?: boolean
|
|
12
|
-
}
|
|
13
|
-
defineProps<{
|
|
14
|
-
options: ColorSelectItem[]
|
|
15
|
-
/** aria-label of the group */
|
|
16
|
-
label?: string
|
|
17
|
-
list?: boolean
|
|
18
|
-
size?: 'sm' | 'md'
|
|
19
|
-
name?: string
|
|
20
|
-
}>()
|
|
21
|
-
const model = defineModel<string>()
|
|
22
|
-
const uid = useUid('oc-color')
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
<template>
|
|
26
|
-
<fieldset :class="['oc-color-select', list && 'oc-color-select--list', size === 'sm' && 'oc-color-select--sm']" role="radiogroup" :aria-label="label">
|
|
27
|
-
<label v-for="option in options" :key="option.value" class="oc-color-select-option" :style="{ '--oc-swatch': option.color }">
|
|
28
|
-
<input v-model="model" type="radio" :name="name ?? uid" :value="option.value" :disabled="option.disabled" />
|
|
29
|
-
<span class="oc-color-select-swatch"></span>
|
|
30
|
-
<span class="oc-color-select-name">{{ option.label }}</span>
|
|
31
|
-
</label>
|
|
32
|
-
</fieldset>
|
|
33
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcColorSelect.vue | Map: types + props -> template (fieldset radiogroup -> label per color) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/** Pick one color from swatches. v-model = selected value. Classes: .oc-color-select(--list/--sm), -option, -swatch, -name */
|
|
4
|
+
import { useUid } from '../composables/useUid'
|
|
5
|
+
|
|
6
|
+
export interface ColorSelectItem {
|
|
7
|
+
value: string
|
|
8
|
+
label: string
|
|
9
|
+
/** CSS color, use Omnia tokens: "var(--color-red-70)". */
|
|
10
|
+
color: string
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
}
|
|
13
|
+
defineProps<{
|
|
14
|
+
options: ColorSelectItem[]
|
|
15
|
+
/** aria-label of the group */
|
|
16
|
+
label?: string
|
|
17
|
+
list?: boolean
|
|
18
|
+
size?: 'sm' | 'md'
|
|
19
|
+
name?: string
|
|
20
|
+
}>()
|
|
21
|
+
const model = defineModel<string>()
|
|
22
|
+
const uid = useUid('oc-color')
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<fieldset :class="['oc-color-select', list && 'oc-color-select--list', size === 'sm' && 'oc-color-select--sm']" role="radiogroup" :aria-label="label">
|
|
27
|
+
<label v-for="option in options" :key="option.value" class="oc-color-select-option" :style="{ '--oc-swatch': option.color }">
|
|
28
|
+
<input v-model="model" type="radio" :name="name ?? uid" :value="option.value" :disabled="option.disabled" />
|
|
29
|
+
<span class="oc-color-select-swatch"></span>
|
|
30
|
+
<span class="oc-color-select-name">{{ option.label }}</span>
|
|
31
|
+
</label>
|
|
32
|
+
</fieldset>
|
|
33
|
+
</template>
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcColumnMenu.vue | Map: scope -> toolbar trigger -> popover (title, form with one checkbox per column, reset) -> default slot (the table) -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Column visibility menu. CSS hides column n of the table in the default slot when [data-col="n"] is unchecked.
|
|
5
|
-
* Checkboxes are uncontrolled, so "Reset" (form reset) restores the defaults. Emits `toggle(column, visible)`.
|
|
6
|
-
* Classes: .oc-column-menu-scope, .oc-column-menu[popover], -title, form.oc-column-menu-list, -item, -footer
|
|
7
|
-
*/
|
|
8
|
-
import { useUid } from '../composables/useUid'
|
|
9
|
-
|
|
10
|
-
withDefaults(
|
|
11
|
-
defineProps<{
|
|
12
|
-
/** One entry per column, in order (max 10). */
|
|
13
|
-
columns: Array<{ label: string; visible?: boolean; locked?: boolean }>
|
|
14
|
-
triggerLabel?: string
|
|
15
|
-
title?: string
|
|
16
|
-
resetLabel?: string
|
|
17
|
-
}>(),
|
|
18
|
-
{ triggerLabel: 'Columns', title: 'Visible columns', resetLabel: 'Reset' },
|
|
19
|
-
)
|
|
20
|
-
const emit = defineEmits<{ toggle: [column: number, visible: boolean] }>()
|
|
21
|
-
const menuId = useUid('oc-column-menu')
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<template>
|
|
25
|
-
<div class="oc-column-menu-scope">
|
|
26
|
-
<div class="oc-table-toolbar">
|
|
27
|
-
<span class="oc-table-toolbar-spacer"></span>
|
|
28
|
-
<button class="oc-btn oc-btn--outline oc-btn--sm" type="button" :popovertarget="menuId">{{ triggerLabel }}</button>
|
|
29
|
-
</div>
|
|
30
|
-
<div :id="menuId" class="oc-column-menu" popover>
|
|
31
|
-
<p class="oc-column-menu-title">{{ title }}</p>
|
|
32
|
-
<form class="oc-column-menu-list">
|
|
33
|
-
<label v-for="(col, i) in columns" :key="i" class="oc-column-menu-item">
|
|
34
|
-
<input
|
|
35
|
-
type="checkbox"
|
|
36
|
-
:data-col="i + 1"
|
|
37
|
-
:checked="col.locked || col.visible !== false"
|
|
38
|
-
:disabled="col.locked"
|
|
39
|
-
@change="emit('toggle', i + 1, ($event.target as HTMLInputElement).checked)"
|
|
40
|
-
/>
|
|
41
|
-
{{ col.label }}
|
|
42
|
-
</label>
|
|
43
|
-
<div class="oc-column-menu-footer">
|
|
44
|
-
<button class="oc-btn oc-btn--ghost oc-btn--sm" type="reset">{{ resetLabel }}</button>
|
|
45
|
-
</div>
|
|
46
|
-
</form>
|
|
47
|
-
</div>
|
|
48
|
-
<slot />
|
|
49
|
-
</div>
|
|
50
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcColumnMenu.vue | Map: scope -> toolbar trigger -> popover (title, form with one checkbox per column, reset) -> default slot (the table) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Column visibility menu. CSS hides column n of the table in the default slot when [data-col="n"] is unchecked.
|
|
5
|
+
* Checkboxes are uncontrolled, so "Reset" (form reset) restores the defaults. Emits `toggle(column, visible)`.
|
|
6
|
+
* Classes: .oc-column-menu-scope, .oc-column-menu[popover], -title, form.oc-column-menu-list, -item, -footer
|
|
7
|
+
*/
|
|
8
|
+
import { useUid } from '../composables/useUid'
|
|
9
|
+
|
|
10
|
+
withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
/** One entry per column, in order (max 10). */
|
|
13
|
+
columns: Array<{ label: string; visible?: boolean; locked?: boolean }>
|
|
14
|
+
triggerLabel?: string
|
|
15
|
+
title?: string
|
|
16
|
+
resetLabel?: string
|
|
17
|
+
}>(),
|
|
18
|
+
{ triggerLabel: 'Columns', title: 'Visible columns', resetLabel: 'Reset' },
|
|
19
|
+
)
|
|
20
|
+
const emit = defineEmits<{ toggle: [column: number, visible: boolean] }>()
|
|
21
|
+
const menuId = useUid('oc-column-menu')
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<div class="oc-column-menu-scope">
|
|
26
|
+
<div class="oc-table-toolbar">
|
|
27
|
+
<span class="oc-table-toolbar-spacer"></span>
|
|
28
|
+
<button class="oc-btn oc-btn--outline oc-btn--sm" type="button" :popovertarget="menuId">{{ triggerLabel }}</button>
|
|
29
|
+
</div>
|
|
30
|
+
<div :id="menuId" class="oc-column-menu" popover>
|
|
31
|
+
<p class="oc-column-menu-title">{{ title }}</p>
|
|
32
|
+
<form class="oc-column-menu-list">
|
|
33
|
+
<label v-for="(col, i) in columns" :key="i" class="oc-column-menu-item">
|
|
34
|
+
<input
|
|
35
|
+
type="checkbox"
|
|
36
|
+
:data-col="i + 1"
|
|
37
|
+
:checked="col.locked || col.visible !== false"
|
|
38
|
+
:disabled="col.locked"
|
|
39
|
+
@change="emit('toggle', i + 1, ($event.target as HTMLInputElement).checked)"
|
|
40
|
+
/>
|
|
41
|
+
{{ col.label }}
|
|
42
|
+
</label>
|
|
43
|
+
<div class="oc-column-menu-footer">
|
|
44
|
+
<button class="oc-btn oc-btn--ghost oc-btn--sm" type="reset">{{ resetLabel }}</button>
|
|
45
|
+
</div>
|
|
46
|
+
</form>
|
|
47
|
+
</div>
|
|
48
|
+
<slot />
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Command palette / searchable list. Filtering, aria-selected and keyboard navigation
|
|
4
|
-
* (ArrowUp/ArrowDown/Enter) are handled here (the CSS only styles the markup).
|
|
5
|
-
* v-model:query is the search text. Emits `select` with the chosen item.
|
|
6
|
-
* Classes: .oc-command, -input, -list, -group-label, -item, -shortcut, -separator, -empty
|
|
7
|
-
*/
|
|
8
|
-
import { computed, ref, watch } from 'vue'
|
|
9
|
-
|
|
10
|
-
export interface CommandItem {
|
|
11
|
-
label: string
|
|
12
|
-
value?: string
|
|
13
|
-
group?: string
|
|
14
|
-
shortcut?: string
|
|
15
|
-
disabled?: boolean
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const props = withDefaults(
|
|
19
|
-
defineProps<{
|
|
20
|
-
items: CommandItem[]
|
|
21
|
-
placeholder?: string
|
|
22
|
-
label?: string
|
|
23
|
-
emptyText?: string
|
|
24
|
-
}>(),
|
|
25
|
-
{ placeholder: 'Type a command or search...', label: 'Search commands', emptyText: 'No results found.' },
|
|
26
|
-
)
|
|
27
|
-
const emit = defineEmits<{ select: [item: CommandItem] }>()
|
|
28
|
-
const query = defineModel<string>('query', { default: '' })
|
|
29
|
-
|
|
30
|
-
const filtered = computed(() => {
|
|
31
|
-
const q = query.value.trim().toLowerCase()
|
|
32
|
-
return q ? props.items.filter((i) => i.label.toLowerCase().includes(q)) : props.items
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const sections = computed(() => {
|
|
36
|
-
const order: string[] = []
|
|
37
|
-
const map = new Map<string, CommandItem[]>()
|
|
38
|
-
for (const item of filtered.value) {
|
|
39
|
-
const key = item.group ?? ''
|
|
40
|
-
if (!map.has(key)) {
|
|
41
|
-
map.set(key, [])
|
|
42
|
-
order.push(key)
|
|
43
|
-
}
|
|
44
|
-
map.get(key)!.push(item)
|
|
45
|
-
}
|
|
46
|
-
return order.map((key) => ({ label: key, items: map.get(key)! }))
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
const flat = computed(() => sections.value.flatMap((s) => s.items))
|
|
50
|
-
const active = ref(0)
|
|
51
|
-
|
|
52
|
-
function firstEnabled(from = 0, step = 1) {
|
|
53
|
-
const list = flat.value
|
|
54
|
-
for (let i = from; i >= 0 && i < list.length; i += step) if (!list[i].disabled) return i
|
|
55
|
-
return -1
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
watch(flat, () => {
|
|
59
|
-
active.value = Math.max(firstEnabled(), 0)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
function move(step: 1 | -1) {
|
|
63
|
-
const next = firstEnabled(active.value + step, step)
|
|
64
|
-
if (next !== -1) active.value = next
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function choose(item: CommandItem) {
|
|
68
|
-
if (!item.disabled) emit('select', item)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function onKeydown(e: KeyboardEvent) {
|
|
72
|
-
if (e.key === 'ArrowDown') {
|
|
73
|
-
e.preventDefault()
|
|
74
|
-
move(1)
|
|
75
|
-
} else if (e.key === 'ArrowUp') {
|
|
76
|
-
e.preventDefault()
|
|
77
|
-
move(-1)
|
|
78
|
-
} else if (e.key === 'Enter') {
|
|
79
|
-
const item = flat.value[active.value]
|
|
80
|
-
if (item) {
|
|
81
|
-
e.preventDefault()
|
|
82
|
-
choose(item)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
</script>
|
|
87
|
-
|
|
88
|
-
<template>
|
|
89
|
-
<div class="oc-command" role="combobox" aria-expanded="true" aria-haspopup="listbox">
|
|
90
|
-
<input
|
|
91
|
-
v-model="query"
|
|
92
|
-
class="oc-command-input"
|
|
93
|
-
type="search"
|
|
94
|
-
:placeholder="placeholder"
|
|
95
|
-
:aria-label="label"
|
|
96
|
-
@keydown="onKeydown"
|
|
97
|
-
/>
|
|
98
|
-
<ul class="oc-command-list" role="listbox">
|
|
99
|
-
<template v-for="(section, s) in sections" :key="section.label + s">
|
|
100
|
-
<li v-if="s > 0" class="oc-command-separator" role="separator"></li>
|
|
101
|
-
<li v-if="section.label" class="oc-command-group-label" role="presentation">{{ section.label }}</li>
|
|
102
|
-
<li
|
|
103
|
-
v-for="item in section.items"
|
|
104
|
-
:key="item.value ?? item.label"
|
|
105
|
-
class="oc-command-item"
|
|
106
|
-
role="option"
|
|
107
|
-
:aria-selected="flat[active] === item ? 'true' : undefined"
|
|
108
|
-
:aria-disabled="item.disabled ? 'true' : undefined"
|
|
109
|
-
@click="choose(item)"
|
|
110
|
-
@mousemove="!item.disabled && (active = flat.indexOf(item))"
|
|
111
|
-
>
|
|
112
|
-
{{ item.label }}
|
|
113
|
-
<span v-if="item.shortcut" class="oc-command-shortcut">{{ item.shortcut }}</span>
|
|
114
|
-
</li>
|
|
115
|
-
</template>
|
|
116
|
-
</ul>
|
|
117
|
-
<div class="oc-command-empty" :hidden="flat.length > 0">{{ emptyText }}</div>
|
|
118
|
-
</div>
|
|
119
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Command palette / searchable list. Filtering, aria-selected and keyboard navigation
|
|
4
|
+
* (ArrowUp/ArrowDown/Enter) are handled here (the CSS only styles the markup).
|
|
5
|
+
* v-model:query is the search text. Emits `select` with the chosen item.
|
|
6
|
+
* Classes: .oc-command, -input, -list, -group-label, -item, -shortcut, -separator, -empty
|
|
7
|
+
*/
|
|
8
|
+
import { computed, ref, watch } from 'vue'
|
|
9
|
+
|
|
10
|
+
export interface CommandItem {
|
|
11
|
+
label: string
|
|
12
|
+
value?: string
|
|
13
|
+
group?: string
|
|
14
|
+
shortcut?: string
|
|
15
|
+
disabled?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const props = withDefaults(
|
|
19
|
+
defineProps<{
|
|
20
|
+
items: CommandItem[]
|
|
21
|
+
placeholder?: string
|
|
22
|
+
label?: string
|
|
23
|
+
emptyText?: string
|
|
24
|
+
}>(),
|
|
25
|
+
{ placeholder: 'Type a command or search...', label: 'Search commands', emptyText: 'No results found.' },
|
|
26
|
+
)
|
|
27
|
+
const emit = defineEmits<{ select: [item: CommandItem] }>()
|
|
28
|
+
const query = defineModel<string>('query', { default: '' })
|
|
29
|
+
|
|
30
|
+
const filtered = computed(() => {
|
|
31
|
+
const q = query.value.trim().toLowerCase()
|
|
32
|
+
return q ? props.items.filter((i) => i.label.toLowerCase().includes(q)) : props.items
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const sections = computed(() => {
|
|
36
|
+
const order: string[] = []
|
|
37
|
+
const map = new Map<string, CommandItem[]>()
|
|
38
|
+
for (const item of filtered.value) {
|
|
39
|
+
const key = item.group ?? ''
|
|
40
|
+
if (!map.has(key)) {
|
|
41
|
+
map.set(key, [])
|
|
42
|
+
order.push(key)
|
|
43
|
+
}
|
|
44
|
+
map.get(key)!.push(item)
|
|
45
|
+
}
|
|
46
|
+
return order.map((key) => ({ label: key, items: map.get(key)! }))
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const flat = computed(() => sections.value.flatMap((s) => s.items))
|
|
50
|
+
const active = ref(0)
|
|
51
|
+
|
|
52
|
+
function firstEnabled(from = 0, step = 1) {
|
|
53
|
+
const list = flat.value
|
|
54
|
+
for (let i = from; i >= 0 && i < list.length; i += step) if (!list[i].disabled) return i
|
|
55
|
+
return -1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
watch(flat, () => {
|
|
59
|
+
active.value = Math.max(firstEnabled(), 0)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
function move(step: 1 | -1) {
|
|
63
|
+
const next = firstEnabled(active.value + step, step)
|
|
64
|
+
if (next !== -1) active.value = next
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function choose(item: CommandItem) {
|
|
68
|
+
if (!item.disabled) emit('select', item)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function onKeydown(e: KeyboardEvent) {
|
|
72
|
+
if (e.key === 'ArrowDown') {
|
|
73
|
+
e.preventDefault()
|
|
74
|
+
move(1)
|
|
75
|
+
} else if (e.key === 'ArrowUp') {
|
|
76
|
+
e.preventDefault()
|
|
77
|
+
move(-1)
|
|
78
|
+
} else if (e.key === 'Enter') {
|
|
79
|
+
const item = flat.value[active.value]
|
|
80
|
+
if (item) {
|
|
81
|
+
e.preventDefault()
|
|
82
|
+
choose(item)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<template>
|
|
89
|
+
<div class="oc-command" role="combobox" aria-expanded="true" aria-haspopup="listbox">
|
|
90
|
+
<input
|
|
91
|
+
v-model="query"
|
|
92
|
+
class="oc-command-input"
|
|
93
|
+
type="search"
|
|
94
|
+
:placeholder="placeholder"
|
|
95
|
+
:aria-label="label"
|
|
96
|
+
@keydown="onKeydown"
|
|
97
|
+
/>
|
|
98
|
+
<ul class="oc-command-list" role="listbox">
|
|
99
|
+
<template v-for="(section, s) in sections" :key="section.label + s">
|
|
100
|
+
<li v-if="s > 0" class="oc-command-separator" role="separator"></li>
|
|
101
|
+
<li v-if="section.label" class="oc-command-group-label" role="presentation">{{ section.label }}</li>
|
|
102
|
+
<li
|
|
103
|
+
v-for="item in section.items"
|
|
104
|
+
:key="item.value ?? item.label"
|
|
105
|
+
class="oc-command-item"
|
|
106
|
+
role="option"
|
|
107
|
+
:aria-selected="flat[active] === item ? 'true' : undefined"
|
|
108
|
+
:aria-disabled="item.disabled ? 'true' : undefined"
|
|
109
|
+
@click="choose(item)"
|
|
110
|
+
@mousemove="!item.disabled && (active = flat.indexOf(item))"
|
|
111
|
+
>
|
|
112
|
+
{{ item.label }}
|
|
113
|
+
<span v-if="item.shortcut" class="oc-command-shortcut">{{ item.shortcut }}</span>
|
|
114
|
+
</li>
|
|
115
|
+
</template>
|
|
116
|
+
</ul>
|
|
117
|
+
<div class="oc-command-empty" :hidden="flat.length > 0">{{ emptyText }}</div>
|
|
118
|
+
</div>
|
|
119
|
+
</template>
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
<!-- File: packages/vue/src/components/OcConfirmDialog.vue | Map: dialog.oc-modal -> confirm content (close X, icon ring, title) -> form (message, typed field, footer: discard, cancel, confirm) -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* Confirm variant of the alert dialog. v-model:open. Emits `confirm`, `cancel`, `discard` (all close the dialog).
|
|
5
|
-
* With `confirmWord` the browser blocks the submit until the typed value matches (pattern + required).
|
|
6
|
-
* Slots: icon, title, default (message).
|
|
7
|
-
* Classes: .oc-alert-dialog--confirm, .oc-alert-dialog-close, .oc-alert-dialog-form, .oc-alert-dialog-typed,
|
|
8
|
-
* .oc-alert-dialog-confirm, .oc-alert-dialog-discard
|
|
9
|
-
*/
|
|
10
|
-
import { ref } from 'vue'
|
|
11
|
-
import { useDialog } from '../composables/useDialog'
|
|
12
|
-
import { useUid } from '../composables/useUid'
|
|
13
|
-
|
|
14
|
-
const props = withDefaults(
|
|
15
|
-
defineProps<{
|
|
16
|
-
type?: 'success' | 'error' | 'warning' | 'info' | 'question'
|
|
17
|
-
title?: string
|
|
18
|
-
message?: string
|
|
19
|
-
confirmLabel?: string
|
|
20
|
-
cancelLabel?: string
|
|
21
|
-
discardLabel?: string
|
|
22
|
-
confirmWord?: string
|
|
23
|
-
closeLabel?: string
|
|
24
|
-
}>(),
|
|
25
|
-
{ type: 'error', confirmLabel: 'Confirm', cancelLabel: 'Cancel', closeLabel: 'Close' },
|
|
26
|
-
)
|
|
27
|
-
const emit = defineEmits<{ confirm: []; cancel: []; discard: [] }>()
|
|
28
|
-
const open = defineModel<boolean>('open', { default: false })
|
|
29
|
-
const el = ref<HTMLDialogElement | null>(null)
|
|
30
|
-
const { onClose } = useDialog(open, el)
|
|
31
|
-
const titleId = useUid('oc-confirm-dialog-title')
|
|
32
|
-
const textId = `${titleId}-text`
|
|
33
|
-
const glyphs = { success: '✓', error: '×', warning: '!', info: 'i', question: '?' } as const
|
|
34
|
-
|
|
35
|
-
// Every action emits its event, then closes through the model (Vue stays the source of truth).
|
|
36
|
-
function done(event: 'confirm' | 'cancel' | 'discard') {
|
|
37
|
-
if (event === 'confirm') emit('confirm')
|
|
38
|
-
else if (event === 'cancel') emit('cancel')
|
|
39
|
-
else emit('discard')
|
|
40
|
-
open.value = false
|
|
41
|
-
}
|
|
42
|
-
const destructive = () => props.type === 'error'
|
|
43
|
-
</script>
|
|
44
|
-
|
|
45
|
-
<template>
|
|
46
|
-
<dialog ref="el" class="oc-modal" closedby="any" :aria-labelledby="titleId" :aria-describedby="textId" @close="onClose">
|
|
47
|
-
<div :class="['oc-modal-content', 'oc-alert-dialog', `oc-alert-dialog--${type}`, 'oc-alert-dialog--confirm']">
|
|
48
|
-
<button class="oc-alert-dialog-close" type="button" :aria-label="closeLabel" @click="done('cancel')">×</button>
|
|
49
|
-
<span class="oc-alert-dialog-icon" aria-hidden="true"><slot name="icon">{{ glyphs[type] }}</slot></span>
|
|
50
|
-
<h2 :id="titleId" class="oc-modal-title"><slot name="title">{{ title }}</slot></h2>
|
|
51
|
-
<form class="oc-alert-dialog-form" @submit.prevent="done('confirm')">
|
|
52
|
-
<div :id="textId" class="oc-modal-body"><slot>{{ message }}</slot></div>
|
|
53
|
-
<label v-if="confirmWord" class="oc-alert-dialog-typed">
|
|
54
|
-
<span>Type <code>{{ confirmWord }}</code> to confirm</span>
|
|
55
|
-
<input class="oc-input" name="confirm" :pattern="confirmWord" required autocomplete="off" />
|
|
56
|
-
</label>
|
|
57
|
-
<div class="oc-modal-footer">
|
|
58
|
-
<button v-if="discardLabel" class="oc-btn oc-btn--ghost oc-alert-dialog-discard" type="button" @click="done('discard')">{{ discardLabel }}</button>
|
|
59
|
-
<button class="oc-btn oc-btn--outline" type="button" @click="done('cancel')">{{ cancelLabel }}</button>
|
|
60
|
-
<button :class="['oc-btn', destructive() && 'oc-btn--destructive', 'oc-alert-dialog-confirm']" type="submit" value="confirm">{{ confirmLabel }}</button>
|
|
61
|
-
</div>
|
|
62
|
-
</form>
|
|
63
|
-
</div>
|
|
64
|
-
</dialog>
|
|
65
|
-
</template>
|
|
1
|
+
<!-- File: packages/vue/src/components/OcConfirmDialog.vue | Map: dialog.oc-modal -> confirm content (close X, icon ring, title) -> form (message, typed field, footer: discard, cancel, confirm) -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* Confirm variant of the alert dialog. v-model:open. Emits `confirm`, `cancel`, `discard` (all close the dialog).
|
|
5
|
+
* With `confirmWord` the browser blocks the submit until the typed value matches (pattern + required).
|
|
6
|
+
* Slots: icon, title, default (message).
|
|
7
|
+
* Classes: .oc-alert-dialog--confirm, .oc-alert-dialog-close, .oc-alert-dialog-form, .oc-alert-dialog-typed,
|
|
8
|
+
* .oc-alert-dialog-confirm, .oc-alert-dialog-discard
|
|
9
|
+
*/
|
|
10
|
+
import { ref } from 'vue'
|
|
11
|
+
import { useDialog } from '../composables/useDialog'
|
|
12
|
+
import { useUid } from '../composables/useUid'
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(
|
|
15
|
+
defineProps<{
|
|
16
|
+
type?: 'success' | 'error' | 'warning' | 'info' | 'question'
|
|
17
|
+
title?: string
|
|
18
|
+
message?: string
|
|
19
|
+
confirmLabel?: string
|
|
20
|
+
cancelLabel?: string
|
|
21
|
+
discardLabel?: string
|
|
22
|
+
confirmWord?: string
|
|
23
|
+
closeLabel?: string
|
|
24
|
+
}>(),
|
|
25
|
+
{ type: 'error', confirmLabel: 'Confirm', cancelLabel: 'Cancel', closeLabel: 'Close' },
|
|
26
|
+
)
|
|
27
|
+
const emit = defineEmits<{ confirm: []; cancel: []; discard: [] }>()
|
|
28
|
+
const open = defineModel<boolean>('open', { default: false })
|
|
29
|
+
const el = ref<HTMLDialogElement | null>(null)
|
|
30
|
+
const { onClose } = useDialog(open, el)
|
|
31
|
+
const titleId = useUid('oc-confirm-dialog-title')
|
|
32
|
+
const textId = `${titleId}-text`
|
|
33
|
+
const glyphs = { success: '✓', error: '×', warning: '!', info: 'i', question: '?' } as const
|
|
34
|
+
|
|
35
|
+
// Every action emits its event, then closes through the model (Vue stays the source of truth).
|
|
36
|
+
function done(event: 'confirm' | 'cancel' | 'discard') {
|
|
37
|
+
if (event === 'confirm') emit('confirm')
|
|
38
|
+
else if (event === 'cancel') emit('cancel')
|
|
39
|
+
else emit('discard')
|
|
40
|
+
open.value = false
|
|
41
|
+
}
|
|
42
|
+
const destructive = () => props.type === 'error'
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<dialog ref="el" class="oc-modal" closedby="any" :aria-labelledby="titleId" :aria-describedby="textId" @close="onClose">
|
|
47
|
+
<div :class="['oc-modal-content', 'oc-alert-dialog', `oc-alert-dialog--${type}`, 'oc-alert-dialog--confirm']">
|
|
48
|
+
<button class="oc-alert-dialog-close" type="button" :aria-label="closeLabel" @click="done('cancel')">×</button>
|
|
49
|
+
<span class="oc-alert-dialog-icon" aria-hidden="true"><slot name="icon">{{ glyphs[type] }}</slot></span>
|
|
50
|
+
<h2 :id="titleId" class="oc-modal-title"><slot name="title">{{ title }}</slot></h2>
|
|
51
|
+
<form class="oc-alert-dialog-form" @submit.prevent="done('confirm')">
|
|
52
|
+
<div :id="textId" class="oc-modal-body"><slot>{{ message }}</slot></div>
|
|
53
|
+
<label v-if="confirmWord" class="oc-alert-dialog-typed">
|
|
54
|
+
<span>Type <code>{{ confirmWord }}</code> to confirm</span>
|
|
55
|
+
<input class="oc-input" name="confirm" :pattern="confirmWord" required autocomplete="off" />
|
|
56
|
+
</label>
|
|
57
|
+
<div class="oc-modal-footer">
|
|
58
|
+
<button v-if="discardLabel" class="oc-btn oc-btn--ghost oc-alert-dialog-discard" type="button" @click="done('discard')">{{ discardLabel }}</button>
|
|
59
|
+
<button class="oc-btn oc-btn--outline" type="button" @click="done('cancel')">{{ cancelLabel }}</button>
|
|
60
|
+
<button :class="['oc-btn', destructive() && 'oc-btn--destructive', 'oc-alert-dialog-confirm']" type="submit" value="confirm">{{ confirmLabel }}</button>
|
|
61
|
+
</div>
|
|
62
|
+
</form>
|
|
63
|
+
</div>
|
|
64
|
+
</dialog>
|
|
65
|
+
</template>
|