我正在学习ReactJS和Material-UI,因此我一直在努力构建一个半Twitter克隆。
我现在遇到了一个障碍,因为我无法确定如何构建可在同一框中容纳所有文本,视频,照片和gif的输入对话框。
使用<Input />
,我可以使用type
分别提供我的输入内容,例如电子邮件,密码等。但是我不确定如何设计这个特殊的对话框以接受多个输入。
您能给我看看一个有效的代码示例吗?
答案 0 :(得分:2)
2019年和很多事情都发生了变化。这是twitter实施的非常粗暴的实现。非常简单。
对于只想看一下代码的人 codesandbox
注意:这非常快地完成,以演示Twitter在幕后所做的事情。
编辑器主要由<textarea />
组成,其中添加了有关推文的文本。
文本区域下方是一个展开的div块,该div循环了从文件系统中选择的图像文件。
对于表情符号,标准的表情符号选择器用于选择表情符号,普通的旧javascript用于在文本区域中当前光标位置添加表情符号。
我跳过了gif选择器,因为它类似于图像选择器,唯一的区别是,已打开一个模态以填充giphy中的gif。 giphy的api。可以轻松集成以实现此目标。
以下是编写的组件
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>
);
}
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