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

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