diff --git a/app/javascript/flavours/glitch/components/copy_icon_button.jsx b/app/javascript/flavours/glitch/components/copy_icon_button.jsx new file mode 100644 index 000000000..9c8d5fb8a --- /dev/null +++ b/app/javascript/flavours/glitch/components/copy_icon_button.jsx @@ -0,0 +1,43 @@ +import PropTypes from 'prop-types'; +import { useState, useCallback } from 'react'; + +import { defineMessages } from 'react-intl'; + +import classNames from 'classnames'; + +import { useDispatch } from 'react-redux'; + +import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react'; +import { showAlert } from 'flavours/glitch/actions/alerts'; +import { IconButton } from 'flavours/glitch/components/icon_button'; + +const messages = defineMessages({ + copied: { id: 'copy_icon_button.copied', defaultMessage: 'Copied to clipboard' }, +}); + +export const CopyIconButton = ({ title, value, className }) => { + const [copied, setCopied] = useState(false); + const dispatch = useDispatch(); + + const handleClick = useCallback(() => { + navigator.clipboard.writeText(value); + setCopied(true); + dispatch(showAlert({ message: messages.copied })); + setTimeout(() => setCopied(false), 700); + }, [setCopied, value, dispatch]); + + return ( + + ); +}; + +CopyIconButton.propTypes = { + title: PropTypes.string, + value: PropTypes.string, + className: PropTypes.string, +}; diff --git a/app/javascript/flavours/glitch/features/account/components/header.jsx b/app/javascript/flavours/glitch/features/account/components/header.jsx index ea56c9638..29e3aa32f 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.jsx +++ b/app/javascript/flavours/glitch/features/account/components/header.jsx @@ -14,9 +14,11 @@ import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react'; import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react'; +import ShareIcon from '@/material-icons/400-24px/share.svg?react'; import { Avatar } from 'flavours/glitch/components/avatar'; import { Badge, AutomatedBadge, GroupBadge } from 'flavours/glitch/components/badge'; import { Button } from 'flavours/glitch/components/button'; +import { CopyIconButton } from 'flavours/glitch/components/copy_icon_button'; import { Icon } from 'flavours/glitch/components/icon'; import { IconButton } from 'flavours/glitch/components/icon_button'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; @@ -44,6 +46,7 @@ const messages = defineMessages({ mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, report: { id: 'account.report', defaultMessage: 'Report @{name}' }, share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' }, + copy: { id: 'account.copy', defaultMessage: 'Copy link to profile' }, media: { id: 'account.media', defaultMessage: 'Media' }, blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, @@ -175,11 +178,10 @@ class Header extends ImmutablePureComponent { const isRemote = account.get('acct') !== account.get('username'); const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null; - let info = []; - let actionBtn = ''; - let bellBtn = ''; - let lockedIcon = ''; - let menu = []; + let actionBtn, bellBtn, lockedIcon, shareBtn; + + let info = []; + let menu = []; if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) { info.push(); @@ -197,6 +199,12 @@ class Header extends ImmutablePureComponent { bellBtn = ; } + if ('share' in navigator) { + shareBtn = ; + } else { + shareBtn = ; + } + if (me !== account.get('id')) { if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded actionBtn = ''; @@ -234,11 +242,6 @@ class Header extends ImmutablePureComponent { menu.push(null); } - if ('share' in navigator && !account.get('suspended')) { - menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); - menu.push(null); - } - if (account.get('id') === me) { if (profileLink) menu.push({ text: intl.formatMessage(messages.edit_profile), href: profileLink }); if (preferencesLink) menu.push({ text: intl.formatMessage(messages.preferences), href: preferencesLink }); @@ -349,6 +352,7 @@ class Header extends ImmutablePureComponent { <> {actionBtn} {bellBtn} + {shareBtn} )} diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index 7df2a3aaf..f51a4c4da 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -1240,6 +1240,17 @@ body > [data-popper-placement] { font-size: 14px; font-weight: 500; } + + &.copyable { + transition: all 300ms linear; + } + + &.copied { + border-color: $valid-value-color; + color: $valid-value-color; + transition: none; + background-color: rgba($valid-value-color, 0.15); + } } .domain__wrapper { @@ -8061,6 +8072,16 @@ noscript { width: 24px; height: 24px; } + + &.copied { + border-color: $valid-value-color; + } + } + + @media screen and (width <= 427px) { + .optional { + display: none; + } } }