vojo/src/app/features/room/room-actions/RoomActions.tsx

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];
}