zhconv 0.4.1

Traditional, Simplified and regional Chinese variants converter powered by MediaWiki & OpenCC rulesets and the Aho-Corasick algorithm 中文简繁及地區詞轉換
Documentation
import React, {
  useState,
  useRef,
  useEffect,
  forwardRef,
  ForwardedRef,
} from "react";
import Grid from "@mui/material/Grid";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import ClickAwayListener from "@mui/material/ClickAwayListener";
import Grow from "@mui/material/Grow";
import Paper from "@mui/material/Paper";
import Popper from "@mui/material/Popper";
import MenuItem from "@mui/material/MenuItem";
import MenuList from "@mui/material/MenuList";
import Box from "@mui/material/Box";
import Tooltip from "@mui/material/Tooltip";

export const variants = {
  zh: "zh 原文",
  "zh-Hant": "zh-Hant 繁體",
  "zh-Hans": "zh-Hans 简体",
  "zh-TW": "zh-TW 臺灣正體",
  "zh-HK": "zh-HK 香港繁體",
  "zh-MO": "zh-MO 澳門繁體",
  "zh-CN": "zh-CN 大陆简体",
  "zh-SG": "zh-SG 新加坡简体",
  "zh-MY": "zh-MY 大马简体",
};
export type Variant = keyof typeof variants;

function ConvertButton(
  {
    onConvert,
    targetVariant,
    setTargetVariant,
    disabled,
  }: {
    onConvert: () => void;
    targetVariant: Variant;
    setTargetVariant: (variant: Variant) => void;
    disabled: boolean;
  },
  ref: ForwardedRef<HTMLButtonElement>,
) {
  const [open, setOpen] = useState(false);
  const anchorRef = useRef<HTMLDivElement>(null);
  // The seemingly redundant anchorEl is used to prevent the lint warning:
  // "Error: Cannot access refs during render"
  const [anchorEl, setAnchorEl] = useState<HTMLDivElement | null>(null);

  useEffect(() => {
    if (anchorRef.current) {
      setAnchorEl(anchorRef.current);
    }
  }, [anchorRef]);

  const handleClick = () => {
    onConvert();
  };

  const handleMenuItemClick = (
    event: React.MouseEvent<HTMLLIElement, MouseEvent>,
    variant: string,
  ) => {
    setOpen(false);
    setTargetVariant(variant as Variant);
  };

  const handleToggle = () => {
    setOpen((prevOpen) => !prevOpen);
  };

  const handleClose = (event: React.MouseEvent<Document, MouseEvent>) => {
    if (
      anchorRef.current &&
      anchorRef.current.contains(event.target as HTMLElement)
    ) {
      return;
    }

    setOpen(false);
  };

  return (
    <Grid container direction="column" alignItems="center">
      <Grid size={{ xs: 12 }}>
        <ButtonGroup
          variant="outlined"
          color="primary"
          ref={anchorRef}
          aria-label="convert button"
          disabled={disabled}
        >
          <Tooltip title="Convert to the target variant / 轉換到此變體">
            <Button ref={ref} onClick={handleClick}>
              <small>To/至</small>
              &nbsp;
              <Box sx={{ whiteSpace: "nowrap" }}>{variants[targetVariant]}</Box>
            </Button>
          </Tooltip>
          <Tooltip title="Select a target variant / 選擇變體">
            <Button
              color="primary"
              size="small"
              aria-controls={open ? "convert-button-menu" : undefined}
              aria-expanded={open ? "true" : undefined}
              aria-label="convert to selected variant"
              aria-haspopup="menu"
              onClick={handleToggle}
            >
              <ArrowDropDownIcon />
            </Button>
          </Tooltip>
        </ButtonGroup>
        <Popper
          open={open}
          anchorEl={anchorEl}
          role={undefined}
          style={{ zIndex: 1900 }}
          transition
          disablePortal
        >
          {({ TransitionProps, placement }) => (
            <Grow
              {...TransitionProps}
              style={{
                transformOrigin:
                  placement === "bottom" ? "center top" : "center bottom",
              }}
            >
              <Paper>
                <ClickAwayListener onClickAway={handleClose}>
                  <MenuList id="convert-button-menu">
                    {Object.entries(variants).map(([variant, option]) => (
                      <MenuItem
                        key={variant}
                        selected={targetVariant === variant}
                        onClick={(event) => handleMenuItemClick(event, variant)}
                        style={{ justifyContent: "center" }}
                      >
                        {option}
                      </MenuItem>
                    ))}
                  </MenuList>
                </ClickAwayListener>
              </Paper>
            </Grow>
          )}
        </Popper>
      </Grid>
    </Grid>
  );
}

export default forwardRef(ConvertButton);