@birdycom/omniaui-vue 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +49 -49
  3. package/dist/omniaui-vue.js.map +1 -1
  4. package/package.json +69 -69
  5. package/src/blocks/OcBlockAboutTimeline.vue +44 -44
  6. package/src/blocks/OcBlockAppHeader.vue +36 -36
  7. package/src/blocks/OcBlockAppShell.vue +29 -29
  8. package/src/blocks/OcBlockAuth.vue +24 -24
  9. package/src/blocks/OcBlockAuthSplit.vue +47 -47
  10. package/src/blocks/OcBlockContactGrid.vue +53 -53
  11. package/src/blocks/OcBlockCta.vue +12 -12
  12. package/src/blocks/OcBlockDataTable.vue +27 -27
  13. package/src/blocks/OcBlockFaq.vue +49 -49
  14. package/src/blocks/OcBlockFeatures.vue +23 -23
  15. package/src/blocks/OcBlockFooter.vue +25 -25
  16. package/src/blocks/OcBlockFormPage.vue +24 -24
  17. package/src/blocks/OcBlockHero.vue +13 -13
  18. package/src/blocks/OcBlockHeroCode.vue +42 -42
  19. package/src/blocks/OcBlockHeroOrbit.vue +69 -69
  20. package/src/blocks/OcBlockHeroSplit.vue +15 -15
  21. package/src/blocks/OcBlockModuleHeader.vue +21 -21
  22. package/src/blocks/OcBlockPageHero.vue +13 -13
  23. package/src/blocks/OcBlockPortfolioGrid.vue +63 -63
  24. package/src/blocks/OcBlockPricing.vue +39 -39
  25. package/src/blocks/OcBlockProcess.vue +39 -39
  26. package/src/blocks/OcBlockProductGrid.vue +41 -41
  27. package/src/blocks/OcBlockProfileHeader.vue +50 -50
  28. package/src/blocks/OcBlockShowcase.vue +43 -43
  29. package/src/blocks/OcBlockSiteFooter.vue +54 -54
  30. package/src/blocks/OcBlockSiteHeader.vue +34 -34
  31. package/src/blocks/OcBlockSpotlight.vue +47 -47
  32. package/src/blocks/OcBlockStats.vue +17 -17
  33. package/src/blocks/OcBlockStatsCounter.vue +20 -20
  34. package/src/blocks/OcBlockTeamList.vue +53 -53
  35. package/src/blocks/OcBlockTwoFactor.vue +61 -61
  36. package/src/blocks/OcBlockWizardDialog.vue +67 -67
  37. package/src/components/OcAccordion.vue +12 -12
  38. package/src/components/OcAccordionAccent.vue +27 -27
  39. package/src/components/OcAccordionItem.vue +19 -19
  40. package/src/components/OcAffixInput.vue +40 -40
  41. package/src/components/OcAlert.vue +17 -17
  42. package/src/components/OcAlertDialog.vue +66 -66
  43. package/src/components/OcAlphaTabs.vue +54 -54
  44. package/src/components/OcAspectRatio.vue +21 -21
  45. package/src/components/OcAttachment.vue +59 -59
  46. package/src/components/OcAttachmentImage.vue +13 -13
  47. package/src/components/OcAttachmentList.vue +31 -31
  48. package/src/components/OcAttachmentThumb.vue +14 -14
  49. package/src/components/OcAvatar.vue +18 -18
  50. package/src/components/OcAvatarGroup.vue +7 -7
  51. package/src/components/OcBackLink.vue +8 -8
  52. package/src/components/OcBadge.vue +13 -13
  53. package/src/components/OcBarcodeInput.vue +51 -51
  54. package/src/components/OcBreadcrumb.vue +22 -22
  55. package/src/components/OcBrowserFrame.vue +33 -33
  56. package/src/components/OcBusyOverlay.vue +32 -32
  57. package/src/components/OcButton.vue +47 -47
  58. package/src/components/OcButtonGroup.vue +10 -10
  59. package/src/components/OcButtonGroupText.vue +7 -7
  60. package/src/components/OcCard.vue +20 -20
  61. package/src/components/OcChartLine.vue +80 -80
  62. package/src/components/OcChatComposer.vue +42 -42
  63. package/src/components/OcCheckbox.vue +26 -26
  64. package/src/components/OcChecklist.vue +29 -29
  65. package/src/components/OcCollapsible.vue +19 -19
  66. package/src/components/OcColorField.vue +15 -15
  67. package/src/components/OcColorSelect.vue +33 -33
  68. package/src/components/OcColumnMenu.vue +50 -50
  69. package/src/components/OcCommand.vue +119 -119
  70. package/src/components/OcConfirmDialog.vue +65 -65
  71. package/src/components/OcCookieConsent.vue +119 -119
  72. package/src/components/OcCopyField.vue +37 -37
  73. package/src/components/OcCounter.vue +15 -15
  74. package/src/components/OcDataTable.vue +144 -144
  75. package/src/components/OcDefinitionList.vue +35 -35
  76. package/src/components/OcDropdown.vue +23 -23
  77. package/src/components/OcDropdownItem.vue +9 -9
  78. package/src/components/OcDropdownLabel.vue +7 -7
  79. package/src/components/OcDropdownSeparator.vue +7 -7
  80. package/src/components/OcDropzone.vue +36 -36
  81. package/src/components/OcEmojiPicker.vue +45 -45
  82. package/src/components/OcEmptyState.vue +13 -13
  83. package/src/components/OcEntityCard.vue +47 -47
  84. package/src/components/OcFactList.vue +37 -37
  85. package/src/components/OcFilterCounts.vue +38 -38
  86. package/src/components/OcFlipCard.vue +36 -36
  87. package/src/components/OcFormField.vue +36 -36
  88. package/src/components/OcGreeting.vue +52 -52
  89. package/src/components/OcHeading.vue +19 -19
  90. package/src/components/OcHoverCard.vue +25 -25
  91. package/src/components/OcImageZoom.vue +39 -39
  92. package/src/components/OcInput.vue +9 -9
  93. package/src/components/OcInputGroup.vue +12 -12
  94. package/src/components/OcItem.vue +40 -40
  95. package/src/components/OcItemGroup.vue +7 -7
  96. package/src/components/OcItemSeparator.vue +7 -7
  97. package/src/components/OcKbd.vue +7 -7
  98. package/src/components/OcKbdGroup.vue +15 -15
  99. package/src/components/OcLabel.vue +10 -10
  100. package/src/components/OcLanguageSelect.vue +40 -40
  101. package/src/components/OcLauncherCard.vue +34 -34
  102. package/src/components/OcLightbox.vue +63 -63
  103. package/src/components/OcLinkArrow.vue +25 -25
  104. package/src/components/OcListToolbar.vue +44 -44
  105. package/src/components/OcMarquee.vue +36 -36
  106. package/src/components/OcMediaCard.vue +28 -28
  107. package/src/components/OcMediaRow.vue +28 -28
  108. package/src/components/OcMediaThumb.vue +38 -38
  109. package/src/components/OcMessage.vue +91 -91
  110. package/src/components/OcMessageList.vue +43 -43
  111. package/src/components/OcMessageThread.vue +155 -155
  112. package/src/components/OcModal.vue +42 -42
  113. package/src/components/OcNavmenu.vue +18 -18
  114. package/src/components/OcNavmenuList.vue +38 -38
  115. package/src/components/OcNextTeaser.vue +25 -25
  116. package/src/components/OcNumberInput.vue +47 -47
  117. package/src/components/OcOtpInput.vue +34 -34
  118. package/src/components/OcPageHeader.vue +34 -34
  119. package/src/components/OcPagination.vue +33 -33
  120. package/src/components/OcPasswordInput.vue +27 -27
  121. package/src/components/OcPasswordRequirements.vue +28 -28
  122. package/src/components/OcPhoneInput.vue +72 -72
  123. package/src/components/OcPopover.vue +25 -25
  124. package/src/components/OcPriceCard.vue +69 -69
  125. package/src/components/OcProductMark.vue +31 -31
  126. package/src/components/OcProgress.vue +21 -21
  127. package/src/components/OcProjectCard.vue +36 -36
  128. package/src/components/OcProse.vue +57 -57
  129. package/src/components/OcRadio.vue +40 -40
  130. package/src/components/OcRadioGroup.vue +32 -32
  131. package/src/components/OcRecoveryCodes.vue +55 -55
  132. package/src/components/OcScrollArea.vue +15 -15
  133. package/src/components/OcSegmented.vue +28 -28
  134. package/src/components/OcSelect.vue +21 -21
  135. package/src/components/OcSeparator.vue +18 -18
  136. package/src/components/OcSeverityCard.vue +46 -46
  137. package/src/components/OcSheet.vue +49 -49
  138. package/src/components/OcSidebarCollapsible.vue +19 -19
  139. package/src/components/OcSidebarGroup.vue +11 -11
  140. package/src/components/OcSidebarMenu.vue +7 -7
  141. package/src/components/OcSidebarMenuButton.vue +31 -31
  142. package/src/components/OcSidebarSeparator.vue +7 -7
  143. package/src/components/OcSkeleton.vue +8 -8
  144. package/src/components/OcSlider.vue +27 -27
  145. package/src/components/OcSortableRow.vue +40 -40
  146. package/src/components/OcSpinner.vue +13 -13
  147. package/src/components/OcStackCard.vue +24 -24
  148. package/src/components/OcStackList.vue +31 -31
  149. package/src/components/OcStatCard.vue +39 -39
  150. package/src/components/OcStepper.vue +34 -34
  151. package/src/components/OcSwitch.vue +15 -15
  152. package/src/components/OcTable.vue +115 -115
  153. package/src/components/OcTableActions.vue +26 -26
  154. package/src/components/OcTableCompact.vue +46 -46
  155. package/src/components/OcTabs.vue +37 -37
  156. package/src/components/OcTagInput.vue +50 -50
  157. package/src/components/OcTerminalLog.vue +50 -50
  158. package/src/components/OcTextarea.vue +9 -9
  159. package/src/components/OcThemeSwitch.vue +31 -31
  160. package/src/components/OcTimeInput.vue +50 -50
  161. package/src/components/OcTimeline.vue +36 -36
  162. package/src/components/OcToast.vue +31 -31
  163. package/src/components/OcToastContainer.vue +21 -21
  164. package/src/components/OcToggle.vue +63 -63
  165. package/src/components/OcToggleField.vue +28 -28
  166. package/src/components/OcToggleGroup.vue +40 -40
  167. package/src/components/OcTooltip.vue +8 -8
  168. package/src/components/OcTopProgress.vue +54 -54
  169. package/src/components/OcTree.vue +70 -70
  170. package/src/components/OcUpgradeBanner.vue +42 -42
  171. package/src/components/OcUser.vue +25 -25
  172. package/src/components/OcVersionBanner.vue +34 -34
  173. package/src/composables/chartLine.ts +28 -28
  174. package/src/composables/keys.ts +22 -22
  175. package/src/composables/useAttrsSplit.ts +16 -16
  176. package/src/composables/useDialog.ts +21 -21
  177. package/src/composables/useToast.ts +66 -66
  178. package/src/composables/useUid.ts +12 -12
@@ -1,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>