@birdycom/omniaui-vue 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +49 -49
  3. package/dist/omniaui-vue.js.map +1 -1
  4. package/package.json +69 -69
  5. package/src/blocks/OcBlockAboutTimeline.vue +44 -44
  6. package/src/blocks/OcBlockAppHeader.vue +36 -36
  7. package/src/blocks/OcBlockAppShell.vue +29 -29
  8. package/src/blocks/OcBlockAuth.vue +24 -24
  9. package/src/blocks/OcBlockAuthSplit.vue +47 -47
  10. package/src/blocks/OcBlockContactGrid.vue +53 -53
  11. package/src/blocks/OcBlockCta.vue +12 -12
  12. package/src/blocks/OcBlockDataTable.vue +27 -27
  13. package/src/blocks/OcBlockFaq.vue +49 -49
  14. package/src/blocks/OcBlockFeatures.vue +23 -23
  15. package/src/blocks/OcBlockFooter.vue +25 -25
  16. package/src/blocks/OcBlockFormPage.vue +24 -24
  17. package/src/blocks/OcBlockHero.vue +13 -13
  18. package/src/blocks/OcBlockHeroCode.vue +42 -42
  19. package/src/blocks/OcBlockHeroOrbit.vue +69 -69
  20. package/src/blocks/OcBlockHeroSplit.vue +15 -15
  21. package/src/blocks/OcBlockModuleHeader.vue +21 -21
  22. package/src/blocks/OcBlockPageHero.vue +13 -13
  23. package/src/blocks/OcBlockPortfolioGrid.vue +63 -63
  24. package/src/blocks/OcBlockPricing.vue +39 -39
  25. package/src/blocks/OcBlockProcess.vue +39 -39
  26. package/src/blocks/OcBlockProductGrid.vue +41 -41
  27. package/src/blocks/OcBlockProfileHeader.vue +50 -50
  28. package/src/blocks/OcBlockShowcase.vue +43 -43
  29. package/src/blocks/OcBlockSiteFooter.vue +54 -54
  30. package/src/blocks/OcBlockSiteHeader.vue +34 -34
  31. package/src/blocks/OcBlockSpotlight.vue +47 -47
  32. package/src/blocks/OcBlockStats.vue +17 -17
  33. package/src/blocks/OcBlockStatsCounter.vue +20 -20
  34. package/src/blocks/OcBlockTeamList.vue +53 -53
  35. package/src/blocks/OcBlockTwoFactor.vue +61 -61
  36. package/src/blocks/OcBlockWizardDialog.vue +67 -67
  37. package/src/components/OcAccordion.vue +12 -12
  38. package/src/components/OcAccordionAccent.vue +27 -27
  39. package/src/components/OcAccordionItem.vue +19 -19
  40. package/src/components/OcAffixInput.vue +40 -40
  41. package/src/components/OcAlert.vue +17 -17
  42. package/src/components/OcAlertDialog.vue +66 -66
  43. package/src/components/OcAlphaTabs.vue +54 -54
  44. package/src/components/OcAspectRatio.vue +21 -21
  45. package/src/components/OcAttachment.vue +59 -59
  46. package/src/components/OcAttachmentImage.vue +13 -13
  47. package/src/components/OcAttachmentList.vue +31 -31
  48. package/src/components/OcAttachmentThumb.vue +14 -14
  49. package/src/components/OcAvatar.vue +18 -18
  50. package/src/components/OcAvatarGroup.vue +7 -7
  51. package/src/components/OcBackLink.vue +8 -8
  52. package/src/components/OcBadge.vue +13 -13
  53. package/src/components/OcBarcodeInput.vue +51 -51
  54. package/src/components/OcBreadcrumb.vue +22 -22
  55. package/src/components/OcBrowserFrame.vue +33 -33
  56. package/src/components/OcBusyOverlay.vue +32 -32
  57. package/src/components/OcButton.vue +47 -47
  58. package/src/components/OcButtonGroup.vue +10 -10
  59. package/src/components/OcButtonGroupText.vue +7 -7
  60. package/src/components/OcCard.vue +20 -20
  61. package/src/components/OcChartLine.vue +80 -80
  62. package/src/components/OcChatComposer.vue +42 -42
  63. package/src/components/OcCheckbox.vue +26 -26
  64. package/src/components/OcChecklist.vue +29 -29
  65. package/src/components/OcCollapsible.vue +19 -19
  66. package/src/components/OcColorField.vue +15 -15
  67. package/src/components/OcColorSelect.vue +33 -33
  68. package/src/components/OcColumnMenu.vue +50 -50
  69. package/src/components/OcCommand.vue +119 -119
  70. package/src/components/OcConfirmDialog.vue +65 -65
  71. package/src/components/OcCookieConsent.vue +119 -119
  72. package/src/components/OcCopyField.vue +37 -37
  73. package/src/components/OcCounter.vue +15 -15
  74. package/src/components/OcDataTable.vue +144 -144
  75. package/src/components/OcDefinitionList.vue +35 -35
  76. package/src/components/OcDropdown.vue +23 -23
  77. package/src/components/OcDropdownItem.vue +9 -9
  78. package/src/components/OcDropdownLabel.vue +7 -7
  79. package/src/components/OcDropdownSeparator.vue +7 -7
  80. package/src/components/OcDropzone.vue +36 -36
  81. package/src/components/OcEmojiPicker.vue +45 -45
  82. package/src/components/OcEmptyState.vue +13 -13
  83. package/src/components/OcEntityCard.vue +47 -47
  84. package/src/components/OcFactList.vue +37 -37
  85. package/src/components/OcFilterCounts.vue +38 -38
  86. package/src/components/OcFlipCard.vue +36 -36
  87. package/src/components/OcFormField.vue +36 -36
  88. package/src/components/OcGreeting.vue +52 -52
  89. package/src/components/OcHeading.vue +19 -19
  90. package/src/components/OcHoverCard.vue +25 -25
  91. package/src/components/OcImageZoom.vue +39 -39
  92. package/src/components/OcInput.vue +9 -9
  93. package/src/components/OcInputGroup.vue +12 -12
  94. package/src/components/OcItem.vue +40 -40
  95. package/src/components/OcItemGroup.vue +7 -7
  96. package/src/components/OcItemSeparator.vue +7 -7
  97. package/src/components/OcKbd.vue +7 -7
  98. package/src/components/OcKbdGroup.vue +15 -15
  99. package/src/components/OcLabel.vue +10 -10
  100. package/src/components/OcLanguageSelect.vue +40 -40
  101. package/src/components/OcLauncherCard.vue +34 -34
  102. package/src/components/OcLightbox.vue +63 -63
  103. package/src/components/OcLinkArrow.vue +25 -25
  104. package/src/components/OcListToolbar.vue +44 -44
  105. package/src/components/OcMarquee.vue +36 -36
  106. package/src/components/OcMediaCard.vue +28 -28
  107. package/src/components/OcMediaRow.vue +28 -28
  108. package/src/components/OcMediaThumb.vue +38 -38
  109. package/src/components/OcMessage.vue +91 -91
  110. package/src/components/OcMessageList.vue +43 -43
  111. package/src/components/OcMessageThread.vue +155 -155
  112. package/src/components/OcModal.vue +42 -42
  113. package/src/components/OcNavmenu.vue +18 -18
  114. package/src/components/OcNavmenuList.vue +38 -38
  115. package/src/components/OcNextTeaser.vue +25 -25
  116. package/src/components/OcNumberInput.vue +47 -47
  117. package/src/components/OcOtpInput.vue +34 -34
  118. package/src/components/OcPageHeader.vue +34 -34
  119. package/src/components/OcPagination.vue +33 -33
  120. package/src/components/OcPasswordInput.vue +27 -27
  121. package/src/components/OcPasswordRequirements.vue +28 -28
  122. package/src/components/OcPhoneInput.vue +72 -72
  123. package/src/components/OcPopover.vue +25 -25
  124. package/src/components/OcPriceCard.vue +69 -69
  125. package/src/components/OcProductMark.vue +31 -31
  126. package/src/components/OcProgress.vue +21 -21
  127. package/src/components/OcProjectCard.vue +36 -36
  128. package/src/components/OcProse.vue +57 -57
  129. package/src/components/OcRadio.vue +40 -40
  130. package/src/components/OcRadioGroup.vue +32 -32
  131. package/src/components/OcRecoveryCodes.vue +55 -55
  132. package/src/components/OcScrollArea.vue +15 -15
  133. package/src/components/OcSegmented.vue +28 -28
  134. package/src/components/OcSelect.vue +21 -21
  135. package/src/components/OcSeparator.vue +18 -18
  136. package/src/components/OcSeverityCard.vue +46 -46
  137. package/src/components/OcSheet.vue +49 -49
  138. package/src/components/OcSidebarCollapsible.vue +19 -19
  139. package/src/components/OcSidebarGroup.vue +11 -11
  140. package/src/components/OcSidebarMenu.vue +7 -7
  141. package/src/components/OcSidebarMenuButton.vue +31 -31
  142. package/src/components/OcSidebarSeparator.vue +7 -7
  143. package/src/components/OcSkeleton.vue +8 -8
  144. package/src/components/OcSlider.vue +27 -27
  145. package/src/components/OcSortableRow.vue +40 -40
  146. package/src/components/OcSpinner.vue +13 -13
  147. package/src/components/OcStackCard.vue +24 -24
  148. package/src/components/OcStackList.vue +31 -31
  149. package/src/components/OcStatCard.vue +39 -39
  150. package/src/components/OcStepper.vue +34 -34
  151. package/src/components/OcSwitch.vue +15 -15
  152. package/src/components/OcTable.vue +115 -115
  153. package/src/components/OcTableActions.vue +26 -26
  154. package/src/components/OcTableCompact.vue +46 -46
  155. package/src/components/OcTabs.vue +37 -37
  156. package/src/components/OcTagInput.vue +50 -50
  157. package/src/components/OcTerminalLog.vue +50 -50
  158. package/src/components/OcTextarea.vue +9 -9
  159. package/src/components/OcThemeSwitch.vue +31 -31
  160. package/src/components/OcTimeInput.vue +50 -50
  161. package/src/components/OcTimeline.vue +36 -36
  162. package/src/components/OcToast.vue +31 -31
  163. package/src/components/OcToastContainer.vue +21 -21
  164. package/src/components/OcToggle.vue +63 -63
  165. package/src/components/OcToggleField.vue +28 -28
  166. package/src/components/OcToggleGroup.vue +40 -40
  167. package/src/components/OcTooltip.vue +8 -8
  168. package/src/components/OcTopProgress.vue +54 -54
  169. package/src/components/OcTree.vue +70 -70
  170. package/src/components/OcUpgradeBanner.vue +42 -42
  171. package/src/components/OcUser.vue +25 -25
  172. package/src/components/OcVersionBanner.vue +34 -34
  173. package/src/composables/chartLine.ts +28 -28
  174. package/src/composables/keys.ts +22 -22
  175. package/src/composables/useAttrsSplit.ts +16 -16
  176. package/src/composables/useDialog.ts +21 -21
  177. package/src/composables/useToast.ts +66 -66
  178. package/src/composables/useUid.ts +12 -12
@@ -1,119 +1,119 @@
1
- <!-- File: packages/vue/src/components/OcCookieConsent.vue | Map: props -> storage helpers -> reopen button -> popover (title, text, categories, actions) -->
2
- <script setup lang="ts">
3
- /**
4
- * Cookie banner on popover="manual" (same markup as snippets/cookie-consent.html). Like the snippet script it shows itself
5
- * when no choice is stored, saves { necessary, <categories>, date } in localStorage and fires "oc-consent" on document.
6
- * Emits: consent (the choice, also on load when one is stored). Slot: text (replaces the paragraph content).
7
- * Classes: .oc-cookie-consent(--bar) __title __text __prefs __category __category-name __category-text __actions
8
- */
9
- import { onBeforeUnmount, onMounted, ref } from 'vue'
10
-
11
- export interface CookieCategory {
12
- name: string
13
- label: string
14
- text?: string
15
- required?: boolean
16
- }
17
- export type ConsentChoice = { necessary: boolean; date: string } & Record<string, boolean | string>
18
-
19
- const props = withDefaults(
20
- defineProps<{
21
- categories?: CookieCategory[]
22
- title?: string
23
- policyHref?: string
24
- customizeLabel?: string
25
- necessaryLabel?: string
26
- selectedLabel?: string
27
- allLabel?: string
28
- /** Text of the "Cookie settings" button; empty string hides it. */
29
- openLabel?: string
30
- bar?: boolean
31
- manual?: boolean
32
- storageKey?: string
33
- id?: string
34
- }>(),
35
- {
36
- categories: () => [
37
- { name: 'necessary', label: 'Necessary', text: 'Session, security and your cookie choice. Always on.', required: true },
38
- { name: 'statistics', label: 'Statistics', text: 'Anonymous visit counts.' },
39
- { name: 'marketing', label: 'Marketing', text: 'Embedded videos and ads that track you.' },
40
- ],
41
- title: 'We use cookies',
42
- policyHref: '#cookie-policy',
43
- customizeLabel: 'Customize',
44
- necessaryLabel: 'Only necessary',
45
- selectedLabel: 'Save choice',
46
- allLabel: 'Accept all',
47
- openLabel: 'Cookie settings',
48
- storageKey: 'oc-consent',
49
- id: 'oc-cookie-consent',
50
- },
51
- )
52
- const emit = defineEmits<{ consent: [choice: ConsentChoice] }>()
53
- defineOptions({ inheritAttrs: false })
54
- const box = ref<HTMLElement | null>(null)
55
-
56
- const read = (): ConsentChoice | null => {
57
- try { return JSON.parse(localStorage.getItem(props.storageKey) ?? 'null') } catch { return null }
58
- }
59
- const inputs = () => Array.from(box.value?.querySelectorAll<HTMLInputElement>('input[name]') ?? [])
60
- function tell(choice: ConsentChoice) {
61
- document.dispatchEvent(new CustomEvent('oc-consent', { detail: choice }))
62
- emit('consent', choice)
63
- }
64
- // Any [data-oc-consent-open] (also outside this component) opens the banner with the stored choice.
65
- function reopen(e: Event) {
66
- if (!(e.target as Element | null)?.closest?.('[data-oc-consent-open]')) return
67
- const choice = read()
68
- for (const i of inputs()) if (!i.disabled) i.checked = !!choice?.[i.name]
69
- box.value?.showPopover?.()
70
- }
71
- function choose(e: MouseEvent) {
72
- const mode = (e.target as HTMLElement).closest<HTMLElement>('[data-oc-consent]')?.dataset.ocConsent
73
- if (!mode) return
74
- const choice: ConsentChoice = { necessary: true, date: new Date().toISOString() }
75
- for (const i of inputs()) if (!i.disabled) choice[i.name] = mode === 'all' || (mode === 'selected' && i.checked)
76
- try { localStorage.setItem(props.storageKey, JSON.stringify(choice)) } catch { /* storage blocked */ }
77
- box.value?.hidePopover?.()
78
- tell(choice)
79
- }
80
- onMounted(() => {
81
- const stored = read()
82
- if (stored) tell(stored)
83
- else if (!props.manual) box.value?.showPopover?.()
84
- document.addEventListener('click', reopen)
85
- })
86
- onBeforeUnmount(() => document.removeEventListener('click', reopen))
87
- </script>
88
-
89
- <template>
90
- <button v-if="openLabel" type="button" class="oc-btn oc-btn--outline oc-btn--sm" data-oc-consent-open>{{ openLabel }}</button>
91
- <aside
92
- :id="id"
93
- ref="box"
94
- v-bind="$attrs"
95
- :class="['oc-cookie-consent', bar && 'oc-cookie-consent--bar']"
96
- popover="manual"
97
- :data-oc-consent-manual="manual ? '' : undefined"
98
- :aria-labelledby="`${id}-title`"
99
- @click="choose"
100
- >
101
- <h2 :id="`${id}-title`" class="oc-cookie-consent__title"><span aria-hidden="true">&#127850;</span> {{ title }}</h2>
102
- <p class="oc-cookie-consent__text">
103
- <slot name="text">Necessary cookies keep the site working. With your permission we also measure visits to improve the site. Read the <a :href="policyHref">cookie policy</a>.</slot>
104
- </p>
105
- <details class="oc-cookie-consent__prefs">
106
- <summary>{{ customizeLabel }}</summary>
107
- <div v-for="c in categories" :key="c.name" class="oc-cookie-consent__category">
108
- <span class="oc-cookie-consent__category-name">{{ c.label }}</span>
109
- <span v-if="c.text" class="oc-cookie-consent__category-text">{{ c.text }}</span>
110
- <label class="oc-switch"><input type="checkbox" role="switch" :name="c.name" :checked="c.required" :disabled="c.required" :aria-label="`${c.label} cookies`" /><span class="oc-switch-track"></span></label>
111
- </div>
112
- </details>
113
- <div class="oc-cookie-consent__actions">
114
- <button type="button" class="oc-btn oc-btn--outline" data-oc-consent="necessary">{{ necessaryLabel }}</button>
115
- <button type="button" class="oc-btn oc-btn--secondary" data-oc-consent="selected">{{ selectedLabel }}</button>
116
- <button type="button" class="oc-btn" data-oc-consent="all">{{ allLabel }}</button>
117
- </div>
118
- </aside>
119
- </template>
1
+ <!-- File: packages/vue/src/components/OcCookieConsent.vue | Map: props -> storage helpers -> reopen button -> popover (title, text, categories, actions) -->
2
+ <script setup lang="ts">
3
+ /**
4
+ * Cookie banner on popover="manual" (same markup as snippets/cookie-consent.html). Like the snippet script it shows itself
5
+ * when no choice is stored, saves { necessary, <categories>, date } in localStorage and fires "oc-consent" on document.
6
+ * Emits: consent (the choice, also on load when one is stored). Slot: text (replaces the paragraph content).
7
+ * Classes: .oc-cookie-consent(--bar) __title __text __prefs __category __category-name __category-text __actions
8
+ */
9
+ import { onBeforeUnmount, onMounted, ref } from 'vue'
10
+
11
+ export interface CookieCategory {
12
+ name: string
13
+ label: string
14
+ text?: string
15
+ required?: boolean
16
+ }
17
+ export type ConsentChoice = { necessary: boolean; date: string } & Record<string, boolean | string>
18
+
19
+ const props = withDefaults(
20
+ defineProps<{
21
+ categories?: CookieCategory[]
22
+ title?: string
23
+ policyHref?: string
24
+ customizeLabel?: string
25
+ necessaryLabel?: string
26
+ selectedLabel?: string
27
+ allLabel?: string
28
+ /** Text of the "Cookie settings" button; empty string hides it. */
29
+ openLabel?: string
30
+ bar?: boolean
31
+ manual?: boolean
32
+ storageKey?: string
33
+ id?: string
34
+ }>(),
35
+ {
36
+ categories: () => [
37
+ { name: 'necessary', label: 'Necessary', text: 'Session, security and your cookie choice. Always on.', required: true },
38
+ { name: 'statistics', label: 'Statistics', text: 'Anonymous visit counts.' },
39
+ { name: 'marketing', label: 'Marketing', text: 'Embedded videos and ads that track you.' },
40
+ ],
41
+ title: 'We use cookies',
42
+ policyHref: '#cookie-policy',
43
+ customizeLabel: 'Customize',
44
+ necessaryLabel: 'Only necessary',
45
+ selectedLabel: 'Save choice',
46
+ allLabel: 'Accept all',
47
+ openLabel: 'Cookie settings',
48
+ storageKey: 'oc-consent',
49
+ id: 'oc-cookie-consent',
50
+ },
51
+ )
52
+ const emit = defineEmits<{ consent: [choice: ConsentChoice] }>()
53
+ defineOptions({ inheritAttrs: false })
54
+ const box = ref<HTMLElement | null>(null)
55
+
56
+ const read = (): ConsentChoice | null => {
57
+ try { return JSON.parse(localStorage.getItem(props.storageKey) ?? 'null') } catch { return null }
58
+ }
59
+ const inputs = () => Array.from(box.value?.querySelectorAll<HTMLInputElement>('input[name]') ?? [])
60
+ function tell(choice: ConsentChoice) {
61
+ document.dispatchEvent(new CustomEvent('oc-consent', { detail: choice }))
62
+ emit('consent', choice)
63
+ }
64
+ // Any [data-oc-consent-open] (also outside this component) opens the banner with the stored choice.
65
+ function reopen(e: Event) {
66
+ if (!(e.target as Element | null)?.closest?.('[data-oc-consent-open]')) return
67
+ const choice = read()
68
+ for (const i of inputs()) if (!i.disabled) i.checked = !!choice?.[i.name]
69
+ box.value?.showPopover?.()
70
+ }
71
+ function choose(e: MouseEvent) {
72
+ const mode = (e.target as HTMLElement).closest<HTMLElement>('[data-oc-consent]')?.dataset.ocConsent
73
+ if (!mode) return
74
+ const choice: ConsentChoice = { necessary: true, date: new Date().toISOString() }
75
+ for (const i of inputs()) if (!i.disabled) choice[i.name] = mode === 'all' || (mode === 'selected' && i.checked)
76
+ try { localStorage.setItem(props.storageKey, JSON.stringify(choice)) } catch { /* storage blocked */ }
77
+ box.value?.hidePopover?.()
78
+ tell(choice)
79
+ }
80
+ onMounted(() => {
81
+ const stored = read()
82
+ if (stored) tell(stored)
83
+ else if (!props.manual) box.value?.showPopover?.()
84
+ document.addEventListener('click', reopen)
85
+ })
86
+ onBeforeUnmount(() => document.removeEventListener('click', reopen))
87
+ </script>
88
+
89
+ <template>
90
+ <button v-if="openLabel" type="button" class="oc-btn oc-btn--outline oc-btn--sm" data-oc-consent-open>{{ openLabel }}</button>
91
+ <aside
92
+ :id="id"
93
+ ref="box"
94
+ v-bind="$attrs"
95
+ :class="['oc-cookie-consent', bar && 'oc-cookie-consent--bar']"
96
+ popover="manual"
97
+ :data-oc-consent-manual="manual ? '' : undefined"
98
+ :aria-labelledby="`${id}-title`"
99
+ @click="choose"
100
+ >
101
+ <h2 :id="`${id}-title`" class="oc-cookie-consent__title"><span aria-hidden="true">&#127850;</span> {{ title }}</h2>
102
+ <p class="oc-cookie-consent__text">
103
+ <slot name="text">Necessary cookies keep the site working. With your permission we also measure visits to improve the site. Read the <a :href="policyHref">cookie policy</a>.</slot>
104
+ </p>
105
+ <details class="oc-cookie-consent__prefs">
106
+ <summary>{{ customizeLabel }}</summary>
107
+ <div v-for="c in categories" :key="c.name" class="oc-cookie-consent__category">
108
+ <span class="oc-cookie-consent__category-name">{{ c.label }}</span>
109
+ <span v-if="c.text" class="oc-cookie-consent__category-text">{{ c.text }}</span>
110
+ <label class="oc-switch"><input type="checkbox" role="switch" :name="c.name" :checked="c.required" :disabled="c.required" :aria-label="`${c.label} cookies`" /><span class="oc-switch-track"></span></label>
111
+ </div>
112
+ </details>
113
+ <div class="oc-cookie-consent__actions">
114
+ <button type="button" class="oc-btn oc-btn--outline" data-oc-consent="necessary">{{ necessaryLabel }}</button>
115
+ <button type="button" class="oc-btn oc-btn--secondary" data-oc-consent="selected">{{ selectedLabel }}</button>
116
+ <button type="button" class="oc-btn" data-oc-consent="all">{{ allLabel }}</button>
117
+ </div>
118
+ </aside>
119
+ </template>
@@ -1,37 +1,37 @@
1
- <script setup lang="ts">
2
- /**
3
- * Read-only monospace value with a copy button. Copies via navigator.clipboard and
4
- * adds .is-copied for 2 seconds. Emits `copy` with the value.
5
- */
6
- import { onBeforeUnmount, ref } from 'vue'
7
-
8
- const props = withDefaults(
9
- defineProps<{
10
- value: string
11
- label?: string
12
- buttonLabel?: string
13
- }>(),
14
- { label: 'Value', buttonLabel: 'Copy' },
15
- )
16
- const emit = defineEmits<{ copy: [value: string] }>()
17
-
18
- const copied = ref(false)
19
- let timer: ReturnType<typeof setTimeout> | undefined
20
-
21
- async function copy() {
22
- await navigator.clipboard.writeText(props.value)
23
- copied.value = true
24
- emit('copy', props.value)
25
- clearTimeout(timer)
26
- timer = setTimeout(() => (copied.value = false), 2000)
27
- }
28
-
29
- onBeforeUnmount(() => clearTimeout(timer))
30
- </script>
31
-
32
- <template>
33
- <div class="oc-copy-field">
34
- <input class="oc-copy-field-value" type="text" readonly :value="value" :aria-label="label" />
35
- <button :class="['oc-copy-field-button', copied && 'is-copied']" type="button" @click="copy">{{ buttonLabel }}</button>
36
- </div>
37
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Read-only monospace value with a copy button. Copies via navigator.clipboard and
4
+ * adds .is-copied for 2 seconds. Emits `copy` with the value.
5
+ */
6
+ import { onBeforeUnmount, ref } from 'vue'
7
+
8
+ const props = withDefaults(
9
+ defineProps<{
10
+ value: string
11
+ label?: string
12
+ buttonLabel?: string
13
+ }>(),
14
+ { label: 'Value', buttonLabel: 'Copy' },
15
+ )
16
+ const emit = defineEmits<{ copy: [value: string] }>()
17
+
18
+ const copied = ref(false)
19
+ let timer: ReturnType<typeof setTimeout> | undefined
20
+
21
+ async function copy() {
22
+ await navigator.clipboard.writeText(props.value)
23
+ copied.value = true
24
+ emit('copy', props.value)
25
+ clearTimeout(timer)
26
+ timer = setTimeout(() => (copied.value = false), 2000)
27
+ }
28
+
29
+ onBeforeUnmount(() => clearTimeout(timer))
30
+ </script>
31
+
32
+ <template>
33
+ <div class="oc-copy-field">
34
+ <input class="oc-copy-field-value" type="text" readonly :value="value" :aria-label="label" />
35
+ <button :class="['oc-copy-field-button', copied && 'is-copied']" type="button" @click="copy">{{ buttonLabel }}</button>
36
+ </div>
37
+ </template>
@@ -1,15 +1,15 @@
1
- <script setup lang="ts">
2
- /** Animated number (@property). Starts when scrolled into view unless `onView` is false. */
3
- import { computed } from 'vue'
4
-
5
- const props = withDefaults(defineProps<{ to: number; suffix?: string; onView?: boolean }>(), { onView: true })
6
- const style = computed(() => {
7
- const s: Record<string, string> = { '--oc-counter-to': String(props.to) }
8
- if (props.suffix) s['--oc-counter-suffix'] = `'${props.suffix}'`
9
- return s
10
- })
11
- </script>
12
-
13
- <template>
14
- <strong :class="['oc-counter', onView && 'oc-counter--on-view']" :style="style"></strong>
15
- </template>
1
+ <script setup lang="ts">
2
+ /** Animated number (@property). Starts when scrolled into view unless `onView` is false. */
3
+ import { computed } from 'vue'
4
+
5
+ const props = withDefaults(defineProps<{ to: number; suffix?: string; onView?: boolean }>(), { onView: true })
6
+ const style = computed(() => {
7
+ const s: Record<string, string> = { '--oc-counter-to': String(props.to) }
8
+ if (props.suffix) s['--oc-counter-suffix'] = `'${props.suffix}'`
9
+ return s
10
+ })
11
+ </script>
12
+
13
+ <template>
14
+ <strong :class="['oc-counter', onView && 'oc-counter--on-view']" :style="style"></strong>
15
+ </template>