Skip to content

Commit 085edf3

Browse files
authored
Merge pull request #7793 from nextcloud-libraries/backport/7787/stable8
[stable8] feat: add NcFormBoxCopyButton
2 parents 3a477e1 + 13e6950 commit 085edf3

File tree

5 files changed

+140
-0
lines changed

5 files changed

+140
-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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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 { mdiCheck, mdiContentCopy } from '@mdi/js'
8+
import { whenever } from '@vueuse/core'
9+
import { computed } from 'vue'
10+
import NcFormBoxButton from '../NcFormBoxButton/NcFormBoxButton.vue'
11+
import NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue'
12+
import { t } from '../../l10n.js'
13+
import { useCopy } from './useCopy.ts'
14+
15+
const props = withDefaults(defineProps<{
16+
/** Copied value's value */
17+
label?: string
18+
/** The value to be copied */
19+
value: string
20+
/** Native disabled attribute */
21+
disabled?: boolean
22+
}>(), {
23+
label: undefined,
24+
disabled: false,
25+
})
26+
27+
const emit = defineEmits<{
28+
copy: () => void
29+
}>()
30+
31+
const { isCopied, copy } = useCopy(() => props.value)
32+
33+
const icon = computed(() => isCopied.value ? mdiCheck : mdiContentCopy)
34+
35+
whenever(isCopied, () => emit('copy'))
36+
</script>
37+
38+
<template>
39+
<NcFormBoxButton
40+
:disabled="disabled"
41+
inverted-accent
42+
@click="copy">
43+
<template v-if="$slots.default || label" #default>
44+
<!-- @slot Custom label content -->
45+
<span class="hidden-visually">
46+
{{ isCopied ? t('Copied') : t('Copy to clipboard') }}
47+
</span>
48+
<slot>
49+
{{ label }}
50+
</slot>
51+
</template>
52+
<template #description>
53+
{{ value }}
54+
</template>
55+
<template #icon>
56+
<NcIconSvgWrapper :path="icon" inline />
57+
</template>
58+
</NcFormBoxButton>
59+
</template>
60+
61+
<docs>
62+
### General
63+
64+
`NcFormBoxButton` set up to be a copy button.
65+
66+
```vue
67+
<template>
68+
<NcFormGroup label="CalDAV" description="Access Nextcloud calendars from other apps and devices">
69+
<NcFormBox>
70+
<NcFormBoxCopyButton
71+
label="CalDAV URL"
72+
value="https://cloud.example.com/remote.php/dav/" />
73+
<NcFormBoxCopyButton
74+
label="Server Address for iOS and macOS"
75+
value="https://cloud.example.com/remote.php/dav/principals/users/user/" />
76+
</NcFormBox>
77+
</NcFormGroup>
78+
</template>
79+
```
80+
</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: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 '@vueuse/core'
7+
8+
import { toValue } from '@vueuse/core'
9+
import { ref } from 'vue'
10+
11+
const DELAY = 2000
12+
13+
/**
14+
* Copy content to clipboard with copied state
15+
*
16+
* @param content - Content to copy
17+
*/
18+
export function useCopy(content: MaybeRefOrGetter<string>) {
19+
const isCopied = ref(false)
20+
21+
/**
22+
* Copy the content to clipboard
23+
*/
24+
async function copy() {
25+
if (isCopied.value) {
26+
return
27+
}
28+
29+
const value = toValue(content)
30+
try {
31+
await navigator.clipboard.writeText(value)
32+
} catch {
33+
// Fallback for a case when clipboard API is not available or permission denied
34+
prompt('', value)
35+
}
36+
37+
isCopied.value = true
38+
setTimeout(() => {
39+
isCopied.value = false
40+
}, DELAY)
41+
}
42+
43+
return {
44+
isCopied,
45+
copy,
46+
}
47+
}

src/components/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export { default as NcEmojiPicker } from './NcEmojiPicker/index.js'
5959
export { default as NcEmptyContent } from './NcEmptyContent/index.js'
6060
export { default as NcFormBox } from './NcFormBox/index.ts'
6161
export { default as NcFormBoxButton } from './NcFormBoxButton/index.ts'
62+
export { default as NcFormBoxCopyButton } from './NcFormBoxCopyButton/index.ts'
6263
export { default as NcFormGroup } from './NcFormGroup/index.ts'
6364
export { default as NcGuestContent } from './NcGuestContent/index.js'
6465
export { default as NcHeaderButton } from './NcHeaderButton/index.js'

0 commit comments

Comments
 (0)