建立类似Twitter的对话框进行输入

时间:2019-06-12 09:22:23

标签: reactjs material-ui

我正在学习ReactJS和Material-UI,因此我一直在努力构建一个半Twitter克隆。

我现在遇到了一个障碍,因为我无法确定如何构建可在同一框中容纳所有文本,视频,照片和gif的输入对话框。

使用<Input />,我可以使用type分别提供我的输入内容,例如电子邮件,密码等。但是我不确定如何设计这个特殊的对话框以接受多个输入。

您能给我看看一个有效的代码示例吗?

enter image description here

1 个答案:

答案 0 :(得分:2)

2019年和很多事情都发生了变化。这是twitter实施的非常粗暴的实现。非常简单。


工作演示链接

对于只想看一下代码的人 codesandbox


tweetsheetpic

注意:这非常快地完成,以演示Twitter在幕后所做的事情。

编辑器主要由<textarea />组成,其中添加了有关推文的文本。 文本区域下方是一个展开的div块,该div循环了从文件系统中选择的图像文件。

对于表情符号,标准的表情符号选择器用于选择表情符号,普通的旧javascript用于在文本区域中当前光标位置添加表情符号。

我跳过了gif选择器,因为它类似于图像选择器,唯一的区别是,已打开一个模态以填充giphy中的gif。 giphy的api。可以轻松集成以实现此目标。

以下是编写的组件


FileInput组件

FileInput组件用作选择图像的按钮和文件选择器。它是普通的<input type="file" />。隐藏原始样式并显示自定义图标

const FileInput = ({ onChange, children }) => {
  const fileRef = useRef();
  const onPickFile = event => {
    onChange([...event.target.files]);
  };
  return (
    <div
      style={{
        width: "35px",
        height: "35px",
        borderRadius: "3px"
      }}
      onClick={() => fileRef.current.click()}
    >
      {children}
      <input
        multiple
        ref={fileRef}
        onChange={onPickFile}
        type="file"
        style={{ visibility: "hidden" }}
      />
    </div>
  );
};

图像组件

Img组件显示从输入中选择的图像。它使用URL.createObjectURL创建一个可以在img标签内填充的临时本地网址。这是在文本区域下方的推文页面中看到的图像预览。

const Img = ({ file, onRemove, index }) => {
  const [fileUrl, setFileUrl] = useState(null);
  useEffect(() => {
    if (file) {
      setFileUrl(URL.createObjectURL(file));
    }
  }, [file]);

  return fileUrl ? (
    <div style={{ position: "relative", maxWidth: "230px", maxHeight: "95px" }}>
      <img
        style={{
          display: "block",
          maxWidth: "230px",
          maxHeight: "95px",
          width: "auto",
          height: "auto"
        }}
        alt="pic"
        src={fileUrl}
      />
      <div
        onClick={() => onRemove(index)}
        style={{
          position: "absolute",
          right: 0,
          top: 0,
          width: "20px",
          height: "20px",
          borderRadius: "50%",
          background: "black",
          color: "white",
          display: "flex",
          alignItems: "center",
          justifyContent: "center"
        }}
      >
        x
      </div>
    </div>
  ) : null;
};

应用程序(推表)

这是将所有东西缝合在一起的根组件。

function App() {
  const [text, setText] = useState("");
  const [pics, setPics] = useState([]);
  const textAreaRef = useRef();
  const insertAtPos = value => {
    const { current: taRef } = textAreaRef;
    let startPos = taRef.selectionStart;
    let endPos = taRef.selectionEnd;
    taRef.value =
      taRef.value.substring(0, startPos) +
      value.native +
      taRef.value.substring(endPos, taRef.value.length);
  };
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        border: "3px solid",
        borderRadius: "5px",
        width: "600px",
        minHeight: "200px",
        padding: "20px"
      }}
    >
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          flex: 1,
          border: "1px solid",
          borderRadius: "5px",
          margin: "0px"
        }}
      >
        <textarea
          ref={textAreaRef}
          value={text}
          style={{ flex: 1, border: "none", minHeight: "150px" }}
          onChange={e => setText(e.target.value)}
        />
        <div
          style={{
            display: "flex",
            flexDirection: "row",
            flexWrap: "wrap",
            background: "fbfbfb"
          }}
        >
          {pics.map((picFile, index) => (
            <Img
              key={index}
              index={index}
              file={picFile}
              onRemove={rmIndx =>
                setPics(pics.filter((pic, index) => index !== rmIndx))
              }
            />
          ))}
        </div>
      </div>
      <div
        style={{
          display: "flex",
          flexDirection: "row",
          alignItems: "center",
          marginTop: "20px"
        }}
      >
        <div style={{ marginRight: "20px" }}>
          <FileInput onChange={pics => setPics(pics)}>
            <ImgIcon />
          </FileInput>
        </div>
        <EmojiPicker onSelect={insertAtPos} />
      </div>
    </div>
  );
}

EmojiPickerModal

const EmojiPicker = ({ onSelect }) => {
  const [show, setShow] = useState(false);
  return (
    <>
      <button
        onClick={() => setShow(oldState => !oldState)}
        style={{
          width: "30px",
          height: "30px",
          borderRadius: "4px",
          border: "3px solid",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          background: "transparent"
        }}
      >
        ej
      </button>
      {ReactDOM.createPortal(
        show && <Picker onSelect={onSelect} />,
        document.body
      )}
    </>
  );
};

注意:对于表情符号选择器,使用了流行的开源表情符号选择器组件emoji-mart