Spam Reporting UI changes
This commit is contained in:
parent
e031d136a1
commit
8387f938eb
88 changed files with 2711 additions and 807 deletions
216
ts/components/SafetyTipsModal.tsx
Normal file
216
ts/components/SafetyTipsModal.tsx
Normal file
|
@ -0,0 +1,216 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import type { UIEvent } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import uuid from 'uuid';
|
||||
import type { LocalizerType } from '../types/I18N';
|
||||
import { Modal } from './Modal';
|
||||
import { Button, ButtonVariant } from './Button';
|
||||
import { useReducedMotion } from '../hooks/useReducedMotion';
|
||||
|
||||
export type SafetyTipsModalProps = Readonly<{
|
||||
i18n: LocalizerType;
|
||||
onClose(): void;
|
||||
}>;
|
||||
|
||||
export function SafetyTipsModal({
|
||||
i18n,
|
||||
onClose,
|
||||
}: SafetyTipsModalProps): JSX.Element {
|
||||
const pages = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
key: 'crypto',
|
||||
title: i18n('icu:SafetyTipsModal__TipTitle--Crypto'),
|
||||
description: i18n('icu:SafetyTipsModal__TipDescription--Crypto'),
|
||||
imageUrl: 'images/safety-tips/safety-tip-crypto.png',
|
||||
},
|
||||
{
|
||||
key: 'vague',
|
||||
title: i18n('icu:SafetyTipsModal__TipTitle--Vague'),
|
||||
description: i18n('icu:SafetyTipsModal__TipDescription--Vague'),
|
||||
imageUrl: 'images/safety-tips/safety-tip-vague.png',
|
||||
},
|
||||
{
|
||||
key: 'links',
|
||||
title: i18n('icu:SafetyTipsModal__TipTitle--Links'),
|
||||
description: i18n('icu:SafetyTipsModal__TipDescription--Links'),
|
||||
imageUrl: 'images/safety-tips/safety-tip-links.png',
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
title: i18n('icu:SafetyTipsModal__TipTitle--Business'),
|
||||
description: i18n('icu:SafetyTipsModal__TipDescription--Business'),
|
||||
imageUrl: 'images/safety-tips/safety-tip-business.png',
|
||||
},
|
||||
];
|
||||
}, [i18n]);
|
||||
|
||||
const [modalId] = useState(() => uuid());
|
||||
const [cardWrapperId] = useState(() => uuid());
|
||||
|
||||
function getCardIdForPage(pageIndex: number) {
|
||||
return `${cardWrapperId}_${pages[pageIndex].key}`;
|
||||
}
|
||||
|
||||
const maxPageIndex = pages.length - 1;
|
||||
const [pageIndex, setPageIndexInner] = useState(0);
|
||||
const reducedMotion = useReducedMotion();
|
||||
const scrollEndTimer = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const [hasPageIndexChanged, setHasPageIndexChanged] = useState(false);
|
||||
function setPageIndex(nextPageIndex: number) {
|
||||
setPageIndexInner(nextPageIndex);
|
||||
setHasPageIndexChanged(true);
|
||||
}
|
||||
|
||||
function clearScrollEndTimer() {
|
||||
if (scrollEndTimer.current != null) {
|
||||
clearTimeout(scrollEndTimer.current);
|
||||
scrollEndTimer.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearScrollEndTimer();
|
||||
};
|
||||
}, []);
|
||||
|
||||
function scrollToPageIndex(nextPageIndex: number) {
|
||||
clearScrollEndTimer();
|
||||
setPageIndex(nextPageIndex);
|
||||
document.getElementById(getCardIdForPage(nextPageIndex))?.scrollIntoView({
|
||||
inline: 'center',
|
||||
behavior: reducedMotion ? 'instant' : 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
function handleScroll(event: UIEvent) {
|
||||
clearScrollEndTimer();
|
||||
const { scrollWidth, scrollLeft, clientWidth } = event.currentTarget;
|
||||
const maxScrollLeft = scrollWidth - clientWidth;
|
||||
const absScrollLeft = Math.abs(scrollLeft);
|
||||
const percentScrolled = absScrollLeft / maxScrollLeft;
|
||||
const scrolledPageIndex = Math.round(percentScrolled * maxPageIndex);
|
||||
scrollEndTimer.current = setTimeout(() => {
|
||||
setPageIndex(scrolledPageIndex);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
i18n={i18n}
|
||||
modalName="SafetyTipsModal"
|
||||
moduleClassName="SafetyTipsModal"
|
||||
noMouseClose
|
||||
hasXButton
|
||||
padded={false}
|
||||
title={i18n('icu:SafetyTipsModal__Title')}
|
||||
onClose={onClose}
|
||||
aria-describedby={`${modalId}-description`}
|
||||
modalFooter={
|
||||
<>
|
||||
<Button
|
||||
className="SafetyTipsModal__Button SafetyTipsModal__Button--Previous"
|
||||
variant={ButtonVariant.SecondaryAffirmative}
|
||||
aria-disabled={pageIndex === 0}
|
||||
aria-controls={cardWrapperId}
|
||||
onClick={() => {
|
||||
if (pageIndex === 0) {
|
||||
return;
|
||||
}
|
||||
scrollToPageIndex(pageIndex - 1);
|
||||
}}
|
||||
>
|
||||
{i18n('icu:SafetyTipsModal__Button--Previous')}
|
||||
</Button>
|
||||
{pageIndex < maxPageIndex ? (
|
||||
<Button
|
||||
className="SafetyTipsModal__Button SafetyTipsModal__Button--Next"
|
||||
variant={ButtonVariant.Primary}
|
||||
aria-controls={cardWrapperId}
|
||||
onClick={() => {
|
||||
if (pageIndex === maxPageIndex) {
|
||||
return;
|
||||
}
|
||||
scrollToPageIndex(pageIndex + 1);
|
||||
}}
|
||||
>
|
||||
{i18n('icu:SafetyTipsModal__Button--Next')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
className="SafetyTipsModal__Button SafetyTipsModal__Button--Next"
|
||||
variant={ButtonVariant.Primary}
|
||||
onClick={onClose}
|
||||
>
|
||||
{i18n('icu:SafetyTipsModal__Button--Done')}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<p className="SafetyTipsModal__Description" id={`${modalId}-description`}>
|
||||
{i18n('icu:SafetyTipsModal__Description')}
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
className="SafetyTipsModal__CardWrapper"
|
||||
id={cardWrapperId}
|
||||
aria-live={hasPageIndexChanged ? 'assertive' : undefined}
|
||||
aria-atomic
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
{pages.map((page, index) => {
|
||||
const isCurrentPage = pageIndex === index;
|
||||
return (
|
||||
<div
|
||||
id={getCardIdForPage(index)}
|
||||
key={page.key}
|
||||
className="SafetyTipsModal__Card"
|
||||
aria-hidden={!isCurrentPage}
|
||||
>
|
||||
<img
|
||||
role="presentation"
|
||||
alt=""
|
||||
className="SafetyTipsModal__CardImage"
|
||||
src={page.imageUrl}
|
||||
width={664}
|
||||
height={314}
|
||||
/>
|
||||
<h2 className="SafetyTipsModal__CardTitle">{page.title}</h2>
|
||||
<p className="SafetyTipsModal__CardDescription">
|
||||
{page.description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="SafetyTipsModal__Dots">
|
||||
{pages.map((page, index) => {
|
||||
const isCurrentPage = pageIndex === index;
|
||||
return (
|
||||
<button
|
||||
key={page.key}
|
||||
className="SafetyTipsModal__DotsButton"
|
||||
type="button"
|
||||
aria-controls={cardWrapperId}
|
||||
aria-current={isCurrentPage ? 'step' : undefined}
|
||||
onClick={() => {
|
||||
scrollToPageIndex(index);
|
||||
}}
|
||||
>
|
||||
<div className="SafetyTipsModal__DotsButtonLabel">
|
||||
{i18n('icu:SafetyTipsModal__DotLabel', {
|
||||
page: index + 1,
|
||||
})}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue