Files
Desktop/ts/components/stickers/StickerManagerPackRow.dom.tsx
T

237 lines
7.1 KiB
TypeScript

// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import {
memo,
useState,
useCallback,
type MouseEvent,
type KeyboardEvent,
type ReactNode,
type JSX,
} from 'react';
import type { LocalizerType } from '../../types/Util.std.ts';
import type { StickerPackType } from '../../state/ducks/stickers.preload.ts';
import { UserText } from '../UserText.dom.tsx';
import { AxoConfirmDialog } from '../../axo/AxoConfirmDialog.dom.tsx';
import { AxoIconButton } from '../../axo/AxoIconButton.dom.tsx';
import { tw } from '../../axo/tw.dom.tsx';
import { OfficialChatInlineBadge } from '../conversation/OfficialChatInlineBadge.dom.tsx';
import { AxoContextMenu } from '../../axo/AxoContextMenu.dom.tsx';
import { AxoSymbol } from '../../axo/AxoSymbol.dom.tsx';
import { ListBoxItem } from 'react-aria-components';
export type OwnProps = {
readonly i18n: LocalizerType;
readonly pack: StickerPackType;
readonly controlType: StickerManagerPackRowControlType;
readonly onClickPreview?: (packId: string) => unknown;
readonly installStickerPack?: (
packId: string,
packKey: string,
options: { actionSource: 'ui' }
) => unknown;
readonly uninstallStickerPack?: (
packId: string,
packKey: string,
options: { actionSource: 'ui' }
) => unknown;
};
export type Props = OwnProps;
export type StickerManagerPackRowControlType = 'install-button' | 'drag-handle';
export const StickerManagerPackRow = memo(function StickerManagerPackRowInner({
controlType,
i18n,
pack,
installStickerPack,
uninstallStickerPack,
onClickPreview,
}: Props) {
const { id, key, isBlessed, title } = pack;
const [uninstalling, setUninstalling] = useState(false);
const clearUninstalling = useCallback(() => {
setUninstalling(false);
}, [setUninstalling]);
const handleInstall = useCallback(
(e: Event | MouseEvent) => {
e.stopPropagation();
if (installStickerPack) {
installStickerPack(id, key, { actionSource: 'ui' });
}
},
[id, installStickerPack, key]
);
const handleUninstall = useCallback(
(e: Event) => {
e.stopPropagation();
if (isBlessed && uninstallStickerPack) {
uninstallStickerPack(id, key, { actionSource: 'ui' });
} else {
setUninstalling(true);
}
},
[id, isBlessed, key, setUninstalling, uninstallStickerPack]
);
const handleConfirmUninstall = useCallback(() => {
clearUninstalling();
if (uninstallStickerPack) {
uninstallStickerPack(id, key, { actionSource: 'ui' });
}
}, [id, key, clearUninstalling, uninstallStickerPack]);
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
if (onClickPreview && (event.key === 'Enter' || event.key === 'Space')) {
event.stopPropagation();
event.preventDefault();
onClickPreview(id);
}
},
[onClickPreview, id]
);
const handleClickPreview = useCallback(
(event: MouseEvent) => {
if (onClickPreview) {
event.stopPropagation();
event.preventDefault();
onClickPreview(id);
}
},
[onClickPreview, id]
);
return (
<ListBoxItem id={id} textValue={title}>
<AxoConfirmDialog.Root
open={uninstalling}
onOpenChange={setUninstalling}
// @ts-expect-error ConfirmationDialog migration: Needs title
title={null}
description={i18n('icu:stickers--StickerManager--UninstallWarning')}
>
<AxoConfirmDialog.Cancel />
<AxoConfirmDialog.Action
variant="strong-destructive"
onClick={handleConfirmUninstall}
>
{i18n('icu:stickers--StickerManager--Uninstall')}
</AxoConfirmDialog.Action>
</AxoConfirmDialog.Root>
<PackContextMenu
i18n={i18n}
handleInstall={handleInstall}
handleUninstall={handleUninstall}
isInstalled={pack.status === 'installed'}
>
<div
tabIndex={0}
// This can't be a button because we have buttons as descendants
role="button"
onKeyDown={handleKeyDown}
onClick={handleClickPreview}
className="module-sticker-manager__pack-row"
data-testid={id}
>
{pack.cover ? (
<img
src={pack.cover.url}
alt={pack.title}
className="module-sticker-manager__pack-row__cover"
/>
) : (
<div className="module-sticker-manager__pack-row__cover-placeholder" />
)}
<div className="module-sticker-manager__pack-row__meta">
<div
className={tw('mb-0.5 flex flex-1 type-body-medium text-primary')}
>
<UserText text={pack.title} />
{pack.isBlessed ? (
<span className={tw('ms-1')}>
<OfficialChatInlineBadge />
</span>
) : null}
</div>
<div className={tw('flex flex-1 type-body-small text-secondary')}>
{pack.author}
</div>
</div>
{controlType === 'install-button' && (
<div className="module-sticker-manager__pack-row__controls">
{pack.status === 'installed' ? (
<AxoIconButton.Root
variant="strong-secondary"
size="md"
symbol="check"
label={i18n('icu:stickers--StickerManager--Installed')}
tooltip={false}
disabled
/>
) : (
<AxoIconButton.Root
variant="strong-secondary"
size="md"
symbol="plus"
label={i18n('icu:stickers--StickerManager--Install')}
onClick={handleInstall}
/>
)}
</div>
)}
{controlType === 'drag-handle' && <ItemDragHandle i18n={i18n} />}
</div>
</PackContextMenu>
</ListBoxItem>
);
});
function PackContextMenu(props: {
i18n: LocalizerType;
isInstalled: boolean;
handleInstall: (e: Event) => void;
handleUninstall: (e: Event) => void;
children: ReactNode;
}) {
const { i18n, isInstalled, handleInstall, handleUninstall } = props;
return (
<AxoContextMenu.Root>
<AxoContextMenu.Trigger>{props.children}</AxoContextMenu.Trigger>
<AxoContextMenu.Content>
{isInstalled ? (
<AxoContextMenu.Item symbol="minus-circle" onSelect={handleUninstall}>
{i18n('icu:stickers--StickerManagerPackContextMenu--Remove')}
</AxoContextMenu.Item>
) : (
<AxoContextMenu.Item symbol="plus-circle" onSelect={handleInstall}>
{i18n('icu:stickers--StickerManagerPackContextMenu--Add')}
</AxoContextMenu.Item>
)}
</AxoContextMenu.Content>
</AxoContextMenu.Root>
);
}
function ItemDragHandle(props: { i18n: LocalizerType }): JSX.Element {
const { i18n } = props;
return (
<span className={tw('cursor-grab text-primary')}>
<AxoSymbol.Icon
symbol="draghandle-alt"
size={18}
label={i18n('icu:stickers--StickerManagerPackDragHandle--Label')}
/>
</span>
);
}