128 lines
4.3 KiB
TypeScript
128 lines
4.3 KiB
TypeScript
import React, { MouseEventHandler, ReactNode } from 'react';
|
|
import { Icon, Icons, IconSrc, Text, color } from 'folds';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useAtom, useSetAtom } from 'jotai';
|
|
import { RoomNotificationMode } from '../../../hooks/useRoomsNotificationPreferences';
|
|
import { botFailedAtomFamily, botShowChatAtomFamily } from '../../bots/botExperienceState';
|
|
import { getBotPath } from '../../../pages/pathUtils';
|
|
import * as css from './RoomActions.css';
|
|
|
|
// Shared row vocabulary for the room overflow (⋮) menu. One chrome only — the
|
|
// anchored folds PopOut — used on both desktop and mobile (the old native
|
|
// bottom sheet was removed). Rows are flat on the popout's dark-blue Vojo
|
|
// surface (SurfaceVariant.Container, the composer tone) with single-accent
|
|
// discipline: violet on Invite, brick on Leave, green on the e2ee chip.
|
|
|
|
type ActionRowProps = {
|
|
icon: IconSrc;
|
|
iconFilled?: boolean;
|
|
label: string;
|
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
trailing?: ReactNode;
|
|
accent?: 'primary' | 'critical';
|
|
disabled?: boolean;
|
|
ariaPressed?: boolean;
|
|
ariaHasPopup?: boolean;
|
|
};
|
|
|
|
// The one flat row: a reset <button> with a fixed-width leading icon slot, an
|
|
// ellipsising label, and an optional trailing cluster (mode word / badge /
|
|
// chevron). Accent tints both the icon (via currentColor) and the label.
|
|
export function ActionRow({
|
|
icon,
|
|
iconFilled,
|
|
label,
|
|
onClick,
|
|
trailing,
|
|
accent,
|
|
disabled,
|
|
ariaPressed,
|
|
ariaHasPopup,
|
|
}: ActionRowProps) {
|
|
let accentColor: string | undefined;
|
|
if (accent === 'primary') accentColor = color.Primary.Main;
|
|
else if (accent === 'critical') accentColor = color.Critical.Main;
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
className={css.ActionRow}
|
|
onClick={onClick}
|
|
disabled={disabled}
|
|
aria-pressed={ariaPressed}
|
|
aria-haspopup={ariaHasPopup}
|
|
style={accentColor ? { color: accentColor } : undefined}
|
|
>
|
|
<span className={css.ActionRowIcon}>
|
|
<Icon size="100" src={icon} filled={iconFilled} />
|
|
</span>
|
|
<Text as="span" size="T300" className={css.ActionRowLabel}>
|
|
{label}
|
|
</Text>
|
|
{trailing && <span className={css.ActionRowTrailing}>{trailing}</span>}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
// Chevron for rows that open a sub-surface (notifications, pinned, jump, invite).
|
|
export function RowChevron() {
|
|
return <Icon className={css.ActionRowChevron} size="100" src={Icons.ChevronRight} />;
|
|
}
|
|
|
|
// Muted trailing word (e.g. the current notification mode beside the row).
|
|
export function RowTrailingText({ children }: { children: ReactNode }) {
|
|
return (
|
|
<Text as="span" size="T200" className={css.ActionRowTrailingText}>
|
|
{children}
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
// Subtle Fleet hairline between row groups.
|
|
export function ActionSectionLine() {
|
|
return <div aria-hidden className={css.SectionLine} />;
|
|
}
|
|
|
|
type BotWidgetActionRowProps = {
|
|
roomId: string;
|
|
botId: string;
|
|
requestClose: () => void;
|
|
};
|
|
// Bot-control DM only: jump back to the BotShell widget. Navigates to
|
|
// /bots/:botId so the shell mounts even from a plain /direct route that
|
|
// happens to be the bot's control room.
|
|
export function BotWidgetActionRow({ roomId, botId, requestClose }: BotWidgetActionRowProps) {
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const setShowChat = useSetAtom(botShowChatAtomFamily(roomId));
|
|
const [failed, setFailed] = useAtom(botFailedAtomFamily(roomId));
|
|
|
|
const handleClick = () => {
|
|
setFailed(false);
|
|
setShowChat(false);
|
|
navigate(getBotPath(botId));
|
|
requestClose();
|
|
};
|
|
|
|
return (
|
|
<ActionRow
|
|
icon={Icons.Terminal}
|
|
label={t(failed ? 'Bots.retry_widget' : 'Bots.show_widget')}
|
|
onClick={handleClick}
|
|
trailing={<RowChevron />}
|
|
/>
|
|
);
|
|
}
|
|
|
|
// Labels the Notifications row's current mode.
|
|
export function useNotificationModeLabel(mode: RoomNotificationMode): string {
|
|
const { t } = useTranslation();
|
|
const labels: Record<RoomNotificationMode, string> = {
|
|
[RoomNotificationMode.Unset]: t('Inbox.notif_default'),
|
|
[RoomNotificationMode.AllMessages]: t('Inbox.notif_all_messages'),
|
|
[RoomNotificationMode.SpecialMessages]: t('Inbox.notif_mentions_keywords'),
|
|
[RoomNotificationMode.Mute]: t('Inbox.notif_mute'),
|
|
};
|
|
return labels[mode];
|
|
}
|