Zanam
Zanam

Reputation: 4807

Building Twitter Like Dialogbox for input

I am learning ReactJS and Material-UI and hence I have been working to build a semi twitter clone.

I am now hitting a road block in the sense that I can't figure how to build this input dialog box which can take all of text, video, photo, gif in same box.

Using <Input /> I can provide individually what kind of input I have i.e. email, password, etc using type. But I am not sure how to design this particular dialog box to take multiple inputs.

Can you please show me a working code example ?

enter image description here

Upvotes: 2

Views: 1677

Answers (1)

johnny peter
johnny peter

Reputation: 4882

Its 2019 and a lot of things have changed. This is a very rough and hacky implementation of how twitter has implemented. Its pretty simple.


Working Demo Link

For folks who want to just look at the code head over to codesandbox


tweetsheetpic

Note: this was done very quickly to demonstrate what twitter has done under the hood.

The editor mainly consists of a <textarea /> where text for the tweets are added. Below the text area is an expanding div block which loops image files that are selected from file system.

As for the emoji, a standard emoji picker is used to select emojis and plain old javascript is used to add emojis at the current cursor position in the textarea.

I have skipped the gif picker, as its similar to the image picker, the only difference being, a modal is open to populate gifs from giphy. An api from giphy. can be easily integrated to achieve this.

The following are the components written


FileInput Component

The FileInput component is used as the button and file picker to select images. Its a plain <input type="file" />. The native styles are hidden and a custom icon is displayed

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 Component

The Img component displays the images selected from the input. It uses URL.createObjectURL to create a temporary local url that can be populated inside an img tag. This is the image preview seen in the tweet sheet below the textarea.

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

App (Tweet Sheet)

This is the root component that stitches everything together.

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
      )}
    </>
  );
};

Note: For the emoji picker a popular open source emoji picker component emoji-mart was used

Upvotes: 3

Related Questions