diff --git a/frontend/src/Components/Form/FormInputButton.js b/frontend/src/Components/Form/FormInputButton.js
index a7145363a..2bacc3779 100644
--- a/frontend/src/Components/Form/FormInputButton.js
+++ b/frontend/src/Components/Form/FormInputButton.js
@@ -42,7 +42,9 @@ function FormInputButton(props) {
FormInputButton.propTypes = {
className: PropTypes.string.isRequired,
isLastButton: PropTypes.bool.isRequired,
- canSpin: PropTypes.bool.isRequired
+ canSpin: PropTypes.bool.isRequired,
+ children: PropTypes.element,
+ id: PropTypes.string
};
FormInputButton.defaultProps = {
diff --git a/frontend/src/Components/Link/ClipboardButton.js b/frontend/src/Components/Link/ClipboardButton.js
deleted file mode 100644
index 55843f05f..000000000
--- a/frontend/src/Components/Link/ClipboardButton.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import Clipboard from 'clipboard';
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import FormInputButton from 'Components/Form/FormInputButton';
-import Icon from 'Components/Icon';
-import { icons, kinds } from 'Helpers/Props';
-import getUniqueElememtId from 'Utilities/getUniqueElementId';
-import styles from './ClipboardButton.css';
-
-class ClipboardButton extends Component {
-
- //
- // Lifecycle
-
- constructor(props, context) {
- super(props, context);
-
- this._id = getUniqueElememtId();
- this._successTimeout = null;
- this._testResultTimeout = null;
-
- this.state = {
- showSuccess: false,
- showError: false
- };
- }
-
- componentDidMount() {
- this._clipboard = new Clipboard(`#${this._id}`, {
- text: () => this.props.value,
- container: document.getElementById(this._id)
- });
-
- this._clipboard.on('success', this.onSuccess);
- }
-
- componentDidUpdate() {
- const {
- showSuccess,
- showError
- } = this.state;
-
- if (showSuccess || showError) {
- this._testResultTimeout = setTimeout(this.resetState, 3000);
- }
- }
-
- componentWillUnmount() {
- if (this._clipboard) {
- this._clipboard.destroy();
- }
-
- if (this._testResultTimeout) {
- clearTimeout(this._testResultTimeout);
- }
- }
-
- //
- // Control
-
- resetState = () => {
- this.setState({
- showSuccess: false,
- showError: false
- });
- };
-
- //
- // Listeners
-
- onSuccess = () => {
- this.setState({
- showSuccess: true
- });
- };
-
- onError = () => {
- this.setState({
- showError: true
- });
- };
-
- //
- // Render
-
- render() {
- const {
- value,
- className,
- ...otherProps
- } = this.props;
-
- const {
- showSuccess,
- showError
- } = this.state;
-
- const showStateIcon = showSuccess || showError;
- const iconName = showError ? icons.DANGER : icons.CHECK;
- const iconKind = showError ? kinds.DANGER : kinds.SUCCESS;
-
- return (
-
-
- {
- showSuccess &&
-
-
-
- }
-
- {
-
-
-
- }
-
-
- );
- }
-}
-
-ClipboardButton.propTypes = {
- className: PropTypes.string.isRequired,
- value: PropTypes.string.isRequired
-};
-
-ClipboardButton.defaultProps = {
- className: styles.button
-};
-
-export default ClipboardButton;
diff --git a/frontend/src/Components/Link/ClipboardButton.tsx b/frontend/src/Components/Link/ClipboardButton.tsx
new file mode 100644
index 000000000..09095ae74
--- /dev/null
+++ b/frontend/src/Components/Link/ClipboardButton.tsx
@@ -0,0 +1,69 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import FormInputButton from 'Components/Form/FormInputButton';
+import Icon from 'Components/Icon';
+import { icons, kinds } from 'Helpers/Props';
+import { ButtonProps } from './Button';
+import styles from './ClipboardButton.css';
+
+export interface ClipboardButtonProps extends Omit {
+ value: string;
+}
+
+export type ClipboardState = 'success' | 'error' | null;
+
+export default function ClipboardButton({
+ id,
+ value,
+ className = styles.button,
+ ...otherProps
+}: ClipboardButtonProps) {
+ const [state, setState] = useState(null);
+
+ useEffect(() => {
+ if (!state) {
+ return;
+ }
+
+ const timeoutId = setTimeout(() => {
+ setState(null);
+ }, 3000);
+
+ return () => {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ };
+ }, [state]);
+
+ const handleClick = useCallback(async () => {
+ try {
+ await navigator.clipboard.writeText(value);
+ setState('success');
+ } catch (_) {
+ setState('error');
+ }
+ }, [value]);
+
+ return (
+
+
+ {state ? (
+
+
+
+ ) : null}
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/Utilities/getUniqueElementId.ts b/frontend/src/Utilities/getUniqueElementId.ts
index dae5150b7..1b380851d 100644
--- a/frontend/src/Utilities/getUniqueElementId.ts
+++ b/frontend/src/Utilities/getUniqueElementId.ts
@@ -1,7 +1,9 @@
let i = 0;
-// returns a HTML 4.0 compliant element IDs (http://stackoverflow.com/a/79022)
-
+/**
+ * @deprecated Use React's useId() instead
+ * @returns An HTML 4.0 compliant element IDs (http://stackoverflow.com/a/79022)
+ */
export default function getUniqueElementId() {
return `id-${i++}`;
}
diff --git a/package.json b/package.json
index 4d62f7d2a..e5e0bbb8f 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,6 @@
"@types/react": "18.2.79",
"@types/react-dom": "18.2.25",
"classnames": "2.3.2",
- "clipboard": "2.0.11",
"connected-react-router": "6.9.3",
"element-class": "0.2.2",
"filesize": "10.0.7",
diff --git a/yarn.lock b/yarn.lock
index 2fd9a7e55..f46048a70 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2477,15 +2477,6 @@ clean-stack@^2.0.0:
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
-clipboard@2.0.11:
- version "2.0.11"
- resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.11.tgz#62180360b97dd668b6b3a84ec226975762a70be5"
- integrity sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==
- dependencies:
- good-listener "^1.2.2"
- select "^1.1.2"
- tiny-emitter "^2.0.0"
-
clone-deep@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
@@ -2880,11 +2871,6 @@ del@^6.1.1:
rimraf "^3.0.2"
slash "^3.0.0"
-delegate@^3.1.2:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
- integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
-
detect-node-es@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
@@ -3811,13 +3797,6 @@ globjoin@^0.1.4:
resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==
-good-listener@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
- integrity sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==
- dependencies:
- delegate "^3.1.2"
-
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@@ -6271,11 +6250,6 @@ section-iterator@^2.0.0:
resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a"
integrity sha512-xvTNwcbeDayXotnV32zLb3duQsP+4XosHpb/F+tu6VzEZFmIjzPdNk6/O+QOOx5XTh08KL2ufdXeCO33p380pQ==
-select@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
- integrity sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==
-
"semver@2 || 3 || 4 || 5", semver@^5.6.0:
version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
@@ -6756,11 +6730,6 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
-tiny-emitter@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
- integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
-
tiny-invariant@^1.0.2:
version "1.3.3"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"