diff --git a/web/public/locales/en/main.json b/web/public/locales/en/main.json index 940d789..fc92fb2 100644 --- a/web/public/locales/en/main.json +++ b/web/public/locales/en/main.json @@ -48,7 +48,8 @@ "cancel": "Cancel", "accept": "Accept", "download": "Download", - "copy": "Copy to clipboard", + "copy": "Copy", + "copied": "Copied to clipboard!", "paste": "Paste", "save": "Save", "settings": { diff --git a/web/public/locales/pl/main.json b/web/public/locales/pl/main.json index 53a303b..3e281b2 100644 --- a/web/public/locales/pl/main.json +++ b/web/public/locales/pl/main.json @@ -49,6 +49,7 @@ "accept": "Akceptuj", "download": "Pobierz", "copy": "Kopiuj", + "copied": "Skopiowano do schowka!", "paste": "Wklej", "save": "Zapisz", "settings": { diff --git a/web/src/components/CopyButton.module.scss b/web/src/components/CopyButton.module.scss new file mode 100644 index 0000000..ec900c2 --- /dev/null +++ b/web/src/components/CopyButton.module.scss @@ -0,0 +1,20 @@ +.feedback { + pointer-events: none; + transition: 0.1s ease-in-out all; + background: var(--color-bg); + color: var(--color-fg); + border-radius: 5px; + padding: 5px 10px; + position: absolute; + top: -0.5rem; + left: 50%; + font-weight: normal; + font-size: 0.8rem; + transform: translate(-50%, -100%); + opacity: 0; + width: 9rem; + + &.visible { + opacity: 1; + } +} diff --git a/web/src/components/CopyButton.tsx b/web/src/components/CopyButton.tsx index 99710c4..abbea71 100644 --- a/web/src/components/CopyButton.tsx +++ b/web/src/components/CopyButton.tsx @@ -1,17 +1,36 @@ import React from 'react'; import { useTranslation } from 'react-i18not'; import { IoCopy } from 'react-icons/io5/index.js'; +import { clsx } from 'clsx'; +import { useTimedState } from '../utils/hooks.js'; +import styles from './CopyButton.module.scss'; import { IconButton, IconButtonProps } from './IconButton.js'; export interface CopyButtonProps - extends Omit {} + extends Omit { + onClick: () => void; +} -export const CopyButton: React.FC = ({ ...props }) => { +export const CopyButton: React.FC = ({ + onClick, + ...props +}) => { const { t } = useTranslation(); + const [copied, setCopied] = useTimedState(false); return ( - + { + onClick(); + setCopied(true); + }} + {...props} + title={t('copy')} + > +
+ {t('copied')} +
); diff --git a/web/src/components/IconButton.module.scss b/web/src/components/IconButton.module.scss index 66f0a82..d34f8d2 100644 --- a/web/src/components/IconButton.module.scss +++ b/web/src/components/IconButton.module.scss @@ -6,6 +6,7 @@ padding: 0; color: var(--color-fg-soft); font-size: 1rem; + position: relative; &:hover { color: var(--color-fg); diff --git a/web/src/utils/hooks.ts b/web/src/utils/hooks.ts new file mode 100644 index 0000000..e857d2b --- /dev/null +++ b/web/src/utils/hooks.ts @@ -0,0 +1,20 @@ +import { useRef, useState } from 'react'; + +export function useTimedState( + initialValue: T, + timeoutMs = 2000 +): [T, (value: T) => void] { + const [value, setValue] = useState(initialValue); + const timeoutRef = useRef(); + + return [ + value, + (value: T) => { + clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout(() => { + setValue(initialValue); + }, timeoutMs); + setValue(value); + }, + ]; +}