Skip to content

Commit 0f09953

Browse files
ShGKmebackportbot[bot]
authored andcommitted
feat: add NcFormBoxCopyButton
Signed-off-by: Grigorii K. Shartsev <me@shgk.me> [skip ci]
1 parent 3a477e1 commit 0f09953

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-0
lines changed

l10n/messages.pot

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ msgstr ""
112112
msgid "Connect items to a project to make them easier to find"
113113
msgstr ""
114114

115+
msgid "Copied"
116+
msgstr ""
117+
118+
msgid "Copy to clipboard"
119+
msgstr ""
120+
115121
#. TRANSLATORS: Ctrl key on keyboard (Windows/Linux)
116122
msgid "Ctrl"
117123
msgstr ""
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<script setup lang="ts">
7+
import type { Slot } from 'vue'
8+
9+
import { mdiCheck, mdiContentCopy } from '@mdi/js'
10+
import { whenever } from '@vueuse/core'
11+
import { computed } from 'vue'
12+
import NcFormBoxButton from '../NcFormBoxButton/NcFormBoxButton.vue'
13+
import NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue'
14+
import { t } from '../../l10n.ts'
15+
import { useCopy } from './useCopy.ts'
16+
17+
const {
18+
label = undefined,
19+
value,
20+
disabled = false,
21+
} = defineProps<{
22+
/** Copied value's value */
23+
label?: string
24+
/** The value to be copied */
25+
value: string
26+
/** Native disabled attribute */
27+
disabled?: boolean
28+
}>()
29+
30+
const emit = defineEmits<{
31+
/** Value has been successfully copied */
32+
copy: []
33+
}>()
34+
35+
defineSlots<{
36+
/** Custom label content */
37+
default?: Slot
38+
}>()
39+
40+
const { isCopied, copy } = useCopy(() => value)
41+
42+
const icon = computed(() => isCopied.value ? mdiCheck : mdiContentCopy)
43+
44+
whenever(isCopied, () => emit('copy'))
45+
</script>
46+
47+
<template>
48+
<NcFormBoxButton
49+
:disabled
50+
inverted-accent
51+
@click="copy">
52+
<template v-if="$slots.default || label" #default>
53+
<span class="hidden-visually">
54+
{{ isCopied ? t('Copied') : t('Copy to clipboard') }}
55+
</span>
56+
<slot>
57+
{{ label }}
58+
</slot>
59+
</template>
60+
<template #description>
61+
{{ value }}
62+
</template>
63+
<template #icon>
64+
<NcIconSvgWrapper :path="icon" inline />
65+
</template>
66+
</NcFormBoxButton>
67+
</template>
68+
69+
<docs>
70+
### General
71+
72+
`NcFormBoxButton` set up to be a copy button.
73+
74+
```vue
75+
<template>
76+
<NcFormGroup label="CalDAV" description="Access Nextcloud calendars from other apps and devices">
77+
<NcFormBox>
78+
<NcFormBoxCopyButton
79+
label="CalDAV URL"
80+
value="https://cloud.example.com/remote.php/dav/" />
81+
<NcFormBoxCopyButton
82+
label="Server Address for iOS and macOS"
83+
value="https://cloud.example.com/remote.php/dav/principals/users/user/" />
84+
</NcFormBox>
85+
</NcFormGroup>
86+
</template>
87+
```
88+
</docs>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
export { default } from './NcFormBoxCopyButton.vue'
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { MaybeRefOrGetter } from 'vue'
7+
8+
import { ref, toValue } from 'vue'
9+
10+
const DELAY = 2000
11+
12+
/**
13+
* Copy content to clipboard with copied state
14+
*
15+
* @param content - Content to copy
16+
*/
17+
export function useCopy(content: MaybeRefOrGetter<string>) {
18+
const isCopied = ref(false)
19+
20+
/**
21+
* Copy the content to clipboard
22+
*/
23+
async function copy() {
24+
if (isCopied.value) {
25+
return
26+
}
27+
28+
const value = toValue(content)
29+
try {
30+
await navigator.clipboard.writeText(value)
31+
} catch {
32+
// Fallback for a case when clipboard API is not available or permission denied
33+
prompt('', value)
34+
}
35+
36+
isCopied.value = true
37+
setTimeout(() => {
38+
isCopied.value = false
39+
}, DELAY)
40+
}
41+
42+
return {
43+
isCopied,
44+
copy,
45+
}
46+
}

0 commit comments

Comments
 (0)