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 0000000000..9c8d5fb8a0
--- /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 ea56c9638b..29e3aa32f7 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 7df2a3aaff..d5de4ce4fc 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 {
@@ -7965,6 +7976,7 @@ noscript {
.account__header {
overflow: hidden;
+ container: account-header / inline-size;
&.inactive {
opacity: 0.5;
@@ -8061,6 +8073,16 @@ noscript {
width: 24px;
height: 24px;
}
+
+ &.copied {
+ border-color: $valid-value-color;
+ }
+ }
+
+ @container account-header (max-width: 372px) {
+ .optional {
+ display: none;
+ }
}
}