Skip to content
Snippets Groups Projects
ButtonItem.tsx 7.51 KiB
Newer Older
import { X, Crown } from "@styled-icons/boxicons-regular";
insert's avatar
insert committed
import { observer } from "mobx-react-lite";
insert's avatar
insert committed
import { Presence } from "revolt-api/types/Users";
import { Channel } from "revolt.js/dist/maps/Channels";
import { User } from "revolt.js/dist/maps/Users";
insert's avatar
insert committed

import styles from "./Item.module.scss";
import classNames from "classnames";
import { attachContextMenu } from "preact-context-menu";
import { Localizer, Text } from "preact-i18n";

insert's avatar
insert committed
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
insert's avatar
insert committed
import { stopPropagation } from "../../../lib/stopPropagation";
insert's avatar
insert committed

insert's avatar
insert committed
import { useIntermediate } from "../../../context/intermediate/Intermediate";

import ChannelIcon from "../../common/ChannelIcon";
import Tooltip from "../../common/Tooltip";
import UserIcon from "../../common/user/UserIcon";
import { Username } from "../../common/user/UserShort";
insert's avatar
insert committed
import UserStatus from "../../common/user/UserStatus";
import IconButton from "../../ui/IconButton";

import { Children } from "../../../types/Preact";

type CommonProps = Omit<
    JSX.HTMLAttributes<HTMLDivElement>,
    "children" | "as"
insert's avatar
insert committed
> & {
    active?: boolean;
    alert?: "unread" | "mention";
    alertCount?: number;
insert's avatar
insert committed
};
insert's avatar
insert committed

type UserProps = CommonProps & {
insert's avatar
insert committed
    user: User;
insert's avatar
insert committed
    context?: Channel;
    channel?: Channel;
insert's avatar
insert committed
};
insert's avatar
insert committed

insert's avatar
insert committed
export const UserButton = observer((props: UserProps) => {
    const { active, alert, alertCount, user, context, channel, ...divProps } =
        props;
    const { openScreen } = useIntermediate();

    return (
        <div
            {...divProps}
            className={classNames(styles.item, styles.user)}
            data-active={active}
            data-alert={typeof alert === "string"}
            data-online={
                typeof channel !== "undefined" ||
insert's avatar
insert committed
                (user.online && user.status?.presence !== Presence.Invisible)
            }
            onContextMenu={attachContextMenu("Menu", {
                user: user._id,
                channel: channel?._id,
                unread: alert,
                contextualChannel: context?._id,
            })}>
            <UserIcon
                className={styles.avatar}
                target={user}
                size={32}
                status
            />
            <div className={styles.name}>
                <div>
                    <Username user={user} />
                </div>
                {
                    <div className={styles.subText}>
                        {channel?.last_message && alert ? (
insert's avatar
insert committed
                            (channel.last_message as { short: string }).short
                        ) : (
                            <UserStatus user={user} />
                        )}
                    </div>
                }
            </div>
            <div className={styles.button}>
                {context?.channel_type === "Group" &&
insert's avatar
insert committed
                    context.owner_id === user._id && (
                        <Localizer>
                            <Tooltip
                                content={<Text id="app.main.groups.owner" />}>
                                <Crown size={20} />
                            </Tooltip>
                        </Localizer>
                    )}
                {alert && (
                    <div className={styles.alert} data-style={alert}>
                        {alertCount}
                    </div>
                )}
                {!isTouchscreenDevice && channel && (
                    <IconButton
                        className={styles.icon}
                        onClick={(e) =>
                            stopPropagation(e) &&
                            openScreen({
                                id: "special_prompt",
                                type: "close_dm",
                                target: channel,
                            })
                        }>
                        <X size={24} />
                    </IconButton>
                )}
            </div>
        </div>
    );
insert's avatar
insert committed

type ChannelProps = CommonProps & {
insert's avatar
insert committed
    channel: Channel & { unread?: string };
insert's avatar
insert committed
    user?: User;
    compact?: boolean;
insert's avatar
insert committed
};
insert's avatar
insert committed

insert's avatar
insert committed
export const ChannelButton = observer((props: ChannelProps) => {
    const { active, alert, alertCount, channel, user, compact, ...divProps } =
        props;

    if (channel.channel_type === "SavedMessages") throw "Invalid channel type.";
    if (channel.channel_type === "DirectMessage") {
        if (typeof user === "undefined") throw "No user provided.";
        return <UserButton {...{ active, alert, channel, user }} />;
    }

    const { openScreen } = useIntermediate();

    return (
        <div
            {...divProps}
            data-active={active}
            data-alert={typeof alert === "string"}
insert's avatar
insert committed
            aria-label={channel.name}
            className={classNames(styles.item, { [styles.compact]: compact })}
            onContextMenu={attachContextMenu("Menu", {
                channel: channel._id,
                unread: typeof channel.unread !== "undefined",
            })}>
            <ChannelIcon
                className={styles.avatar}
                target={channel}
                size={compact ? 24 : 32}
            />
            <div className={styles.name}>
                <div>{channel.name}</div>
                {channel.channel_type === "Group" && (
                    <div className={styles.subText}>
                        {channel.last_message && alert ? (
insert's avatar
insert committed
                            (channel.last_message as { short: string }).short
                        ) : (
                            <Text
                                id="quantities.members"
insert's avatar
insert committed
                                plural={channel.recipients!.length}
                                fields={{ count: channel.recipients!.length }}
                            />
                        )}
                    </div>
                )}
            </div>
            <div className={styles.button}>
                {alert && (
                    <div className={styles.alert} data-style={alert}>
                        {alertCount}
                    </div>
                )}
                {!isTouchscreenDevice && channel.channel_type === "Group" && (
                    <IconButton
                        className={styles.icon}
                        onClick={() =>
                            openScreen({
                                id: "special_prompt",
                                type: "leave_group",
                                target: channel,
                            })
                        }>
                        <X size={24} />
                    </IconButton>
                )}
            </div>
        </div>
    );
insert's avatar
insert committed
});
insert's avatar
insert committed

type ButtonProps = CommonProps & {
    onClick?: () => void;
    children?: Children;
    className?: string;
    compact?: boolean;
insert's avatar
insert committed
};
insert's avatar
insert committed

export default function ButtonItem(props: ButtonProps) {
    const {
        active,
        alert,
        alertCount,
        onClick,
        className,
        children,
        compact,
        ...divProps
    } = props;

    return (
        <div
            {...divProps}
            className={classNames(
                styles.item,
                { [styles.compact]: compact, [styles.normal]: !compact },
                className,
            )}
            onClick={onClick}
            data-active={active}
            data-alert={typeof alert === "string"}>
            <div className={styles.content}>{children}</div>
            {alert && (
                <div className={styles.alert} data-style={alert}>
                    {alertCount}
                </div>
            )}
        </div>
    );
insert's avatar
insert committed
}