mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-30 14:56:52 -05:00
Redesign: Display follow button in column header on profile page (#40460)
This commit is contained in:
@@ -3,12 +3,12 @@ import type { FC } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { followAccount } from '@/mastodon/actions/accounts';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { useFollowReference } from '@/mastodon/hooks/useFollowReference';
|
||||
import { getAccountHidden } from '@/mastodon/selectors/accounts';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
import { isRedesignEnabled } from '@/mastodon/utils/environment';
|
||||
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';
|
||||
@@ -83,10 +83,8 @@ const AccountButtonsOther: FC<
|
||||
});
|
||||
}
|
||||
}, [accountUrl]);
|
||||
const { state } = useLocation<{
|
||||
reference?: string;
|
||||
} | null>();
|
||||
const reference = state?.reference ?? 'profile';
|
||||
|
||||
const reference = useFollowReference('profile');
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
@@ -99,8 +97,10 @@ const AccountButtonsOther: FC<
|
||||
<>
|
||||
{!isMovedAndUnfollowedAccount && (
|
||||
<FollowButton
|
||||
compact={isRedesignEnabled()}
|
||||
accountId={accountId}
|
||||
className={classes.followButton}
|
||||
withUnmute={false}
|
||||
labelLength='long'
|
||||
reference={reference}
|
||||
/>
|
||||
|
||||
@@ -107,7 +107,7 @@ const BaseButton: React.FC<BaseButtonProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
type ButtonProps = BaseButtonProps & {
|
||||
export type ButtonProps = BaseButtonProps & {
|
||||
leadingIcon?: IconProp;
|
||||
trailingIcon?: IconProp;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,11 @@ import { me } from 'mastodon/initial_state';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { useBreakpoint } from '../features/ui/hooks/useBreakpoint';
|
||||
import { isRedesignEnabled } from '../utils/environment';
|
||||
|
||||
import { Button as RedesignButton } from './button/redesign';
|
||||
import type { ButtonProps as RedesignButtonProps } from './button/redesign';
|
||||
import type { MastodonLocationDescriptor } from './router';
|
||||
|
||||
const longMessages = defineMessages({
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
@@ -55,21 +60,29 @@ const shortMessages = {
|
||||
}),
|
||||
};
|
||||
|
||||
export const FollowButton: React.FC<{
|
||||
interface FollowButtonOptions {
|
||||
accountId?: string;
|
||||
compact?: boolean;
|
||||
labelLength?: 'auto' | 'short' | 'long';
|
||||
className?: string;
|
||||
withUnmute?: boolean;
|
||||
reference?: string;
|
||||
}> = ({
|
||||
}
|
||||
|
||||
interface FollowButtonReturn {
|
||||
label: React.ReactNode;
|
||||
onClick: (() => void) | undefined;
|
||||
link: MastodonLocationDescriptor | undefined;
|
||||
disabled: boolean;
|
||||
following: boolean;
|
||||
secondary: boolean;
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
export function useFollowButton({
|
||||
accountId,
|
||||
compact,
|
||||
labelLength = 'auto',
|
||||
className,
|
||||
withUnmute = true,
|
||||
reference,
|
||||
}) => {
|
||||
}: FollowButtonOptions): FollowButtonReturn {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { signedIn } = useIdentity();
|
||||
@@ -79,7 +92,9 @@ export const FollowButton: React.FC<{
|
||||
const relationship = useAppSelector((state) =>
|
||||
accountId ? state.relationships.get(accountId) : undefined,
|
||||
);
|
||||
const following = relationship?.following || relationship?.requested;
|
||||
const following = relationship?.following || relationship?.requested || false;
|
||||
const secondary = following || relationship?.blocking || false;
|
||||
const link = accountId === me ? '/profile/edit' : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId && signedIn) {
|
||||
@@ -87,7 +102,7 @@ export const FollowButton: React.FC<{
|
||||
}
|
||||
}, [dispatch, accountId, signedIn]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
const onClick = useCallback(() => {
|
||||
if (!signedIn) {
|
||||
dispatch(
|
||||
openModal({
|
||||
@@ -148,7 +163,7 @@ export const FollowButton: React.FC<{
|
||||
? messages.followRequest
|
||||
: messages.follow;
|
||||
|
||||
let label;
|
||||
let label: React.ReactNode;
|
||||
let disabled =
|
||||
relationship?.blocked_by || account?.suspended || !!account?.moved;
|
||||
|
||||
@@ -176,7 +191,30 @@ export const FollowButton: React.FC<{
|
||||
label = intl.formatMessage(followMessage);
|
||||
}
|
||||
|
||||
if (accountId === me) {
|
||||
const isMovedAndUnfollowedAccount =
|
||||
(account?.moved && !relationship?.following) || false;
|
||||
|
||||
return {
|
||||
onClick,
|
||||
link,
|
||||
label,
|
||||
disabled,
|
||||
following,
|
||||
secondary,
|
||||
hidden: isMovedAndUnfollowedAccount,
|
||||
};
|
||||
}
|
||||
|
||||
const FollowButtonLegacy: React.FC<
|
||||
FollowButtonOptions & {
|
||||
compact?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
> = ({ compact, className, ...props }) => {
|
||||
const { onClick, link, label, disabled, following, secondary } =
|
||||
useFollowButton(props);
|
||||
|
||||
if (link) {
|
||||
const buttonClasses = classNames(className, 'button button-secondary', {
|
||||
'button--compact': compact,
|
||||
});
|
||||
@@ -190,9 +228,9 @@ export const FollowButton: React.FC<{
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
secondary={following || relationship?.blocking}
|
||||
secondary={secondary}
|
||||
compact={compact}
|
||||
className={classNames(className, { 'button--destructive': following })}
|
||||
>
|
||||
@@ -200,3 +238,52 @@ export const FollowButton: React.FC<{
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const FollowButtonRedesign: React.FC<
|
||||
FollowButtonOptions &
|
||||
Pick<RedesignButtonProps, 'size' | 'color' | 'className'>
|
||||
> = ({ accountId, labelLength, withUnmute, reference, ...buttonProps }) => {
|
||||
const { onClick, link, label, disabled, secondary, hidden } = useFollowButton(
|
||||
{
|
||||
accountId,
|
||||
labelLength,
|
||||
withUnmute,
|
||||
reference,
|
||||
},
|
||||
);
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (link) {
|
||||
return (
|
||||
<RedesignButton as='link' to={link} {...buttonProps}>
|
||||
{label}
|
||||
</RedesignButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RedesignButton
|
||||
{...buttonProps}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
variant={secondary ? 'tonal' : 'solid'}
|
||||
>
|
||||
{label}
|
||||
</RedesignButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const FollowButton: React.FC<
|
||||
FollowButtonOptions & {
|
||||
compact?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
> = ({ compact, ...props }) => {
|
||||
if (isRedesignEnabled()) {
|
||||
return <FollowButtonRedesign {...props} size={compact ? 'sm' : 'md'} />;
|
||||
}
|
||||
return <FollowButtonLegacy compact={compact} {...props} />;
|
||||
};
|
||||
|
||||
17
app/javascript/mastodon/hooks/useFollowReference.ts
Normal file
17
app/javascript/mastodon/hooks/useFollowReference.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import type { LocationState } from '../components/router';
|
||||
|
||||
/**
|
||||
* Gets the current `reference` state from location state if present,
|
||||
* otherwise returns the passed-in fallback reference.
|
||||
*
|
||||
* `reference` is a string that we use to anonymously & locally track
|
||||
* where a follow is coming from, helping us understand which UI features
|
||||
* people actually use to discover people.
|
||||
*/
|
||||
export function useFollowReference(fallbackReference: string) {
|
||||
const { state } = useLocation<LocationState>();
|
||||
|
||||
return state?.reference ?? fallbackReference;
|
||||
}
|
||||
Reference in New Issue
Block a user