diff --git a/libs/ui/index.ts b/libs/ui/index.ts
index 19e4235329..e70fb970a0 100644
--- a/libs/ui/index.ts
+++ b/libs/ui/index.ts
@@ -18,6 +18,7 @@ export * from './lib/avatar/Avatar'
export * from './lib/badge/Badge'
export * from './lib/button/Button'
export * from './lib/checkbox/Checkbox'
+export * from './lib/copy-to-clipboard/CopyToClipboard'
export * from './lib/date-picker/DateRangePicker'
export * from './lib/divider/Divider'
export * from './lib/dropdown-menu/DropdownMenu'
diff --git a/libs/ui/lib/copy-to-clipboard/CopyToClipboard.stories.tsx b/libs/ui/lib/copy-to-clipboard/CopyToClipboard.stories.tsx
new file mode 100644
index 0000000000..544d6aeec6
--- /dev/null
+++ b/libs/ui/lib/copy-to-clipboard/CopyToClipboard.stories.tsx
@@ -0,0 +1,28 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * Copyright Oxide Computer Company
+ */
+import { CopyToClipboard } from './CopyToClipboard'
+
+export const Default = () => (
+
+
+ This is the text to be copied.
+
+
+
+ Note that the text rendered on the screen is independent of the text copied to the
+ clipboard …
+
+
+ This text, for example, is different from the text that’ll be copied …
+
+
+
+)
diff --git a/libs/ui/lib/copy-to-clipboard/CopyToClipboard.tsx b/libs/ui/lib/copy-to-clipboard/CopyToClipboard.tsx
new file mode 100644
index 0000000000..c521346eaf
--- /dev/null
+++ b/libs/ui/lib/copy-to-clipboard/CopyToClipboard.tsx
@@ -0,0 +1,44 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * Copyright Oxide Computer Company
+ */
+
+import { useState } from 'react'
+
+import { Clipboard16Icon, Success12Icon, useTimeout } from '@oxide/ui'
+
+export const CopyToClipboard = ({
+ ariaLabel = 'Click to copy this text',
+ text,
+}: {
+ ariaLabel?: string
+ text: string
+}) => {
+ const [hasCopied, setHasCopied] = useState(false)
+
+ useTimeout(() => setHasCopied(false), hasCopied ? 2000 : null)
+
+ const handleCopy = () => {
+ window.navigator.clipboard.writeText(text).then(() => {
+ setHasCopied(true)
+ })
+ }
+
+ return (
+
+ )
+}
diff --git a/libs/ui/lib/truncate/Truncate.tsx b/libs/ui/lib/truncate/Truncate.tsx
index 3764c1634f..6bc0c65cda 100644
--- a/libs/ui/lib/truncate/Truncate.tsx
+++ b/libs/ui/lib/truncate/Truncate.tsx
@@ -5,11 +5,8 @@
*
* Copyright Oxide Computer Company
*/
-import { useState } from 'react'
-import { Clipboard16Icon, Success12Icon } from '@oxide/design-system/icons/react'
-
-import useTimeout from '../hooks/use-timeout'
+import { CopyToClipboard } from '../copy-to-clipboard/CopyToClipboard'
import { Tooltip } from '../tooltip/Tooltip'
type TruncatePosition = 'middle' | 'end'
@@ -29,38 +26,17 @@ export const Truncate = ({
hasCopyButton,
tooltipDelay = 300,
}: TruncateProps) => {
- const [hasCopied, setHasCopied] = useState(false)
-
- useTimeout(() => setHasCopied(false), hasCopied ? 2000 : null)
-
if (text.length <= maxLength) {
return
{text}
}
- const handleCopy = () => {
- window.navigator.clipboard.writeText(text).then(() => {
- setHasCopied(true)
- })
- }
-
// Only use the tooltip if the text is longer than maxLength
return (