Wrapstodon design QA tweaks (#37201)

This commit is contained in:
diondiondion
2025-12-11 12:40:53 +01:00
committed by GitHub
parent d1b996b7e3
commit 5651900b89
8 changed files with 142 additions and 36 deletions

View File

@@ -0,0 +1,45 @@
import { useCallback, useRef } from 'react';
export const InterceptStatusClicks: React.FC<{
onPreventedClick: (
clickedArea: 'account' | 'post',
event: React.MouseEvent,
) => void;
children: React.ReactNode;
}> = ({ onPreventedClick, children }) => {
const wrapperRef = useRef<HTMLDivElement>(null);
const handleClick = useCallback(
(e: React.MouseEvent) => {
const clickTarget = e.target as Element;
const allowedElementsSelector =
'.video-player, .audio-player, .media-gallery, .content-warning';
const allowedElements = wrapperRef.current?.querySelectorAll(
allowedElementsSelector,
);
const isTargetClickAllowed =
allowedElements &&
Array.from(allowedElements).some((element) => {
return clickTarget === element || element.contains(clickTarget);
});
if (!isTargetClickAllowed) {
e.preventDefault();
e.stopPropagation();
const wasAccountAreaClicked = !!clickTarget.closest(
'a.status__display-name',
);
onPreventedClick(wasAccountAreaClicked ? 'account' : 'post', e);
}
},
[onPreventedClick],
);
return (
<div ref={wrapperRef} onClickCapture={handleClick}>
{children}
</div>
);
};

View File

@@ -4,10 +4,14 @@
@typescript-eslint/no-unsafe-member-access,
@typescript-eslint/no-unsafe-call */
import type { ComponentPropsWithoutRef } from 'react';
import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { InterceptStatusClicks } from 'mastodon/components/status/intercept_status_clicks';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
import type { TopStatuses } from 'mastodon/models/annual_report';
import { makeGetStatus } from 'mastodon/selectors';
@@ -29,6 +33,24 @@ export const HighlightedPost: React.FC<{
statusId ? getStatus(state, { id: statusId }) : undefined,
);
const handleClick = useCallback<
ComponentPropsWithoutRef<typeof InterceptStatusClicks>['onPreventedClick']
>(
(clickedArea) => {
const link: string =
clickedArea === 'account'
? status.getIn(['account', 'url'])
: status.get('url');
if (context === 'standalone') {
window.location.href = link;
} else {
window.open(link, '_blank');
}
},
[status, context],
);
if (!status) {
return <div className={classNames(styles.box, styles.mostBoostedPost)} />;
}
@@ -72,7 +94,9 @@ export const HighlightedPost: React.FC<{
{context === 'modal' && <p>{label}</p>}
</div>
<StatusQuoteManager showActions={false} id={statusId} />
<InterceptStatusClicks onPreventedClick={handleClick}>
<StatusQuoteManager showActions={false} id={statusId} />
</InterceptStatusClicks>
</div>
);
};

View File

@@ -21,7 +21,8 @@ $mobile-breakpoint: 540px;
scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary);
@media (width < $mobile-breakpoint) {
padding-inline: 10px;
padding-top: 0;
padding-inline: 0;
}
.loading-indicator .circular-progress {
@@ -50,37 +51,51 @@ $mobile-breakpoint: 540px;
}
.wrapper {
--gradient-strength: 0.4;
box-sizing: border-box;
position: relative;
max-width: 600px;
padding: 24px;
padding-top: 40px;
contain: layout;
flex: 0 0 auto;
pointer-events: auto;
pointer-events: all;
color: var(--color-text-primary);
background: var(--color-bg-primary);
background:
radial-gradient(at 40% 87%, #240c9a99 0, transparent 50%),
radial-gradient(at 19% 10%, #6b0c9a99 0, transparent 50%),
radial-gradient(at 90% 27%, #9a0c8299 0, transparent 50%),
radial-gradient(at 16% 95%, #1e948299 0, transparent 50%),
radial-gradient(at 80% 91%, #16dae499 0, transparent 50%)
radial-gradient(
at 10% 27%,
rgba(83, 12, 154, var(--gradient-strength)) 0,
transparent 50%
),
radial-gradient(
at 91% 10%,
rgba(30, 24, 223, var(--gradient-strength)) 0,
transparent 25%
),
radial-gradient(
at 10% 91%,
rgba(22, 218, 228, var(--gradient-strength)) 0,
transparent 40%
),
radial-gradient(
at 75% 87%,
rgba(37, 31, 217, var(--gradient-strength)) 0,
transparent 20%
),
radial-gradient(
at 84% 60%,
rgba(95, 30, 148, var(--gradient-strength)) 0,
transparent 40%
)
var(--color-bg-primary);
border-radius: 40px;
@media (width < $mobile-breakpoint) {
padding-inline: 12px;
padding-bottom: 12px;
border-radius: 28px;
}
&::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
background: inherit;
border-radius: inherit;
filter: blur(20px);
border-radius: 0;
}
}
@@ -92,7 +107,7 @@ $mobile-breakpoint: 540px;
font-family: silkscreen-wrapstodon, monospace;
font-size: 28px;
line-height: 1;
margin-bottom: 8px;
margin-bottom: 4px;
padding-inline: 40px; // Prevent overlap with close button
@media (width < $mobile-breakpoint) {
@@ -116,7 +131,7 @@ $mobile-breakpoint: 540px;
.box {
position: relative;
padding: 16px;
padding: 24px;
border-radius: 16px;
background: rgb(from var(--color-bg-primary) r g b / 60%);
box-shadow: inset 0 0 0 1px rgb(from var(--color-text-primary) r g b / 40%);
@@ -150,7 +165,6 @@ $mobile-breakpoint: 540px;
flex-direction: column;
justify-content: center;
gap: 8px;
padding: 16px;
font-size: 14px;
text-align: center;
text-wrap: balance;
@@ -164,6 +178,10 @@ $mobile-breakpoint: 540px;
text-transform: uppercase;
color: #c2c8ff;
font-weight: 500;
&:last-child {
margin-bottom: -3px;
}
}
.statLarge {
@@ -185,7 +203,7 @@ $mobile-breakpoint: 540px;
.mostBoostedPost {
padding: 0;
padding-top: 8px;
padding-top: 24px;
overflow: hidden;
}
@@ -260,7 +278,7 @@ $mobile-breakpoint: 540px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
gap: 16px;
p {
max-width: 460px;

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import type { FC } from 'react';
import { defineMessage, useIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import { useLocation } from 'react-router';
@@ -23,11 +23,6 @@ import { NewPosts } from './new_posts';
const moduleClassNames = classNames.bind(styles);
export const shareMessage = defineMessage({
id: 'annual_report.summary.share_message',
defaultMessage: 'I got the {archetype} archetype!',
});
export const AnnualReport: FC<{ context?: 'modal' | 'standalone' }> = ({
context = 'standalone',
}) => {

View File

@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import type { FC } from 'react';
import { useIntl } from 'react-intl';
import { defineMessages, useIntl } from 'react-intl';
import { resetCompose, focusCompose } from '@/mastodon/actions/compose';
import { closeModal } from '@/mastodon/actions/modal';
@@ -9,9 +9,19 @@ import { Button } from '@/mastodon/components/button';
import type { AnnualReport as AnnualReportData } from '@/mastodon/models/annual_report';
import { useAppDispatch } from '@/mastodon/store';
import { shareMessage } from '.';
import { archetypeNames } from './archetype';
const messages = defineMessages({
share_message: {
id: 'annual_report.summary.share_message',
defaultMessage: 'I got the {archetype} archetype!',
},
share_on_mastodon: {
id: 'annual_report.summary.share_on_mastodon',
defaultMessage: 'Share on Mastodon',
},
});
export const ShareButton: FC<{ report: AnnualReportData }> = ({ report }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
@@ -21,7 +31,7 @@ export const ShareButton: FC<{ report: AnnualReportData }> = ({ report }) => {
archetypeNames[report.data.archetype],
);
const shareLines = [
intl.formatMessage(shareMessage, {
intl.formatMessage(messages.share_message, {
archetype: archetypeName,
}),
];
@@ -37,5 +47,10 @@ export const ShareButton: FC<{ report: AnnualReportData }> = ({ report }) => {
dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }));
}, [report, intl, dispatch]);
return <Button text='Share here' onClick={handleShareClick} />;
return (
<Button
text={intl.formatMessage(messages.share_on_mastodon)}
onClick={handleShareClick}
/>
);
};

View File

@@ -1,7 +1,15 @@
$mobile-breakpoint: 540px;
.wrapper {
max-width: max-content;
box-sizing: border-box;
max-width: 600px;
margin-inline: auto;
padding: 40px 10px;
@media (width < $mobile-breakpoint) {
padding-top: 0;
padding-inline: 0;
}
}
.footer {

View File

@@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl';
import { IconLogo } from '@/mastodon/components/logo';
import { AnnualReport } from './index';
import classes from './shared_page.module.css';
import classes from './shared_page.module.scss';
export const WrapstodonSharedPage: FC = () => {
return (

View File

@@ -152,6 +152,7 @@
"annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of {domain} users.</bottomLabel>",
"annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.",
"annual_report.summary.share_message": "I got the {archetype} archetype!",
"annual_report.summary.share_on_mastodon": "Share on Mastodon",
"attachments_list.unprocessed": "(unprocessed)",
"audio.hide": "Hide audio",
"block_modal.remote_users_caveat": "We will ask the server {domain} to respect your decision. However, compliance is not guaranteed since some servers may handle blocks differently. Public posts may still be visible to non-logged-in users.",