<div id="root"></div>
@import url('https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@500&display=swap');
* {
font-family: 'Chakra Petch', sans-serif;
}
body, h1, h2, h3, h4, h5, h6 {
margin: 0;
}
const { useState, useCallback } = React;
const FONT_SIZES = {
xs: ".8vmin",
sm: "1.2vmin",
md: "1.8vmin",
lg: "2vmin"
};
const MARGIN = {
xs: ".8vmin",
sm: "1.4vmin",
md: "1.8vmin",
lg: "3vmin"
};
const COLORS = {
background: "#171734",
white: "#ffffff",
accent: "#ffc107",
bright: "#ffffff",
dark: "#333333",
gray: "#555555",
lightGray: "#bbbbbb",
superLightGray: "#dedede",
red: "#dc3545"
};
const BUTTON_THEMES = {
default: {
background: COLORS.lightGray,
color: COLORS.dark
},
primary: {
background: COLORS.accent,
color: COLORS.dark
},
danger: {
background: COLORS.red,
color: COLORS.white
}
};
const BUTTON_SIZES = {
sm: {
fontSize: FONT_SIZES.sm,
padding: ".6vmin 1.6vmin"
},
md: {
fontSize: FONT_SIZES.md,
padding: "1vmin 2vmin"
},
lg: {
fontSize: FONT_SIZES.lg,
padding: "1.4vmin 2.4vmin"
}
};
const LOCAL_IMAGE_PREVIEWER = {
height: "50vmin",
width: "80vmin"
};
const IMAGE_SIZE_LIMIT = 500;
const useLocalImagePreviewer = (sizeLimit) => {
const [localImage, setLocalImage] = useState(null);
const getLocalFileDataURL = (fileData) => {
return new Promise((resolve) => {
const reader = new FileReader();
console.log(reader)
reader.readAsDataURL(fileData);
reader.addEventListener("load", (e) => {
console.log(e);
const loadedFile = e.target.result;
resolve(loadedFile);
});
});
};
const handleChange = useCallback(async (e) => {
console.log(e.target.files)
const fileData = e.target.files[0];
if (!fileData) return; //エクスプローラでキャンセルしたときにエラーが発生しないように
if (!fileData.type.match("image.*")) {
alert("Please select image");
return;
}
if (sizeLimit) {
if (fileData.size > sizeLimit * 1000) {
alert(`File size should be ${sizeLimit}KB or less.`);
return;
}
}
const loadImageURL = await getLocalFileDataURL(fileData);
setLocalImage(loadImageURL);
}, []);
const clearFilePath = useCallback((e) => {
e.target.value = null;
}, []);
const removeLocalImage = useCallback(() => setLocalImage(null), []);
const handleDrop = useCallback(async (e) => {
e.stopPropagation();
e.preventDefault();
const files = e.dataTransfer.files; // FileList
const fileData = files[0]; // File
if (!fileData.type.match("image.*")) {
alert("Please select image");
return;
}
if (sizeLimit) {
if (fileData.size > sizeLimit * 1000) {
alert(`File size should be ${sizeLimit}KB or less.`);
return;
}
}
const loadImageURL = await getLocalFileDataURL(fileData);
setLocalImage(loadImageURL);
});
const handleDragOver = useCallback((e) => {
e.stopPropagation();
e.preventDefault();
e.dataTransfer.dropEffect = "copy";
}, []);
return {
localImage,
handleChange,
clearFilePath,
removeLocalImage,
handleDragOver,
handleDrop
};
};
const FlexAllCenterStyle = styled.css`
display: flex;
align-items: center;
justify-content: center;
`;
const Button = styled.button`
background-color: ${({ theme }) =>
(theme && theme.background) || BUTTON_THEMES.default.background};
border: none;
border-radius: 0.6vmin;
cursor: ${({ isDisabled }) => (isDisabled ? "cursor" : "pointer")};
color: ${({ theme }) => (theme && theme.color) || BUTTON_THEMES.default.color};
font-size: ${({ size }) =>
(size && size.fontSize) || BUTTON_SIZES.md.fontSize};
//margin-top: ${({ margin }) => (margin && margin.top) || 0};
//margin-right: ${({ margin }) => (margin && margin.right) || 0};
//margin-bottom: ${({ margin }) => (margin && margin.bottom) || 0};
//margin-left: ${({ margin }) => (margin && margin.left) || 0};
outline: none;
opacity: ${({ isDisabled }) => (isDisabled ? 0.5 : 1)};
padding: ${({ size }) => (size && size.padding) || BUTTON_SIZES.md.padding};
pointer-events: ${({ isDisabled }) => (isDisabled ? "none" : "auto")};
user-select: none;
`;
const StyledLocalFileSelectButton = Button.withComponent("label");
const Input = styled.input`
display: none;
`;
const StyledLocalImagePreviewer = styled.div`
background-color: ${({ background }) => background || COLORS.superLightGray};
border-radius: 1vmin;
box-sizing: border-box;
height: ${({ height }) => height};
padding: 2vmin;
width: ${({ width }) => width};
`;
const LocalImage = styled.img`
max-height: 100%;
width: auto;
//position: absolute;
//top: 0;
`;
const DragZone = styled.div`
border-color: ${({ borderColor }) => borderColor || COLORS.lightGray};
border-width: 0.4vmin;
border-style: dashed;
box-sizing: border-box;
${FlexAllCenterStyle};
flex-direction: column;
overflow: hidden;
//position: relative;
height: 100%;
width: 100%;
`;
const Margin = styled.div`
margin-top: ${({ top }) => top || 0};
margin-right: ${({ right }) => right || 0};
margin-bottom: ${({ bottom }) => bottom || 0};
margin-left: ${({ left }) => left || 0};
width: 100%;
`;
const Message = styled.h3`
color: ${COLORS.dark};
font-size: ${FONT_SIZES.md};
`;
const LocalFileSelectButton = ({
className,
theme,
size,
clearFilePath,
handleChange,
accept,
children
}) => (
<StyledLocalFileSelectButton
htmlFor="file"
className={className}
theme={theme}
size={size}
onClick={clearFilePath}
onChange={handleChange}
>
<Input
type="file"
id="file"
name="file"
className="file"
accept={accept}
/>
{children}
</StyledLocalFileSelectButton>
);
const StyledApp = styled.div`
background-color: ${COLORS.background}; //cornflowerblue;
${FlexAllCenterStyle};
height: 100vh;
width: 100%;
`;
const Container = styled.div`
${FlexAllCenterStyle};
flex-direction: column;
padding: 4vmin;
width: 100%;
`;
const LocalImagePreviewer = ({
localImagePreviewer,
height,
width,
background
}) => (
<React.Fragment>
<StyledLocalImagePreviewer
height={height}
width={width}
background={background}
>
<DragZone
onDragOver={localImagePreviewer.handleDragOver}
onDrop={localImagePreviewer.handleDrop}
borderColor={background}
>
{
localImagePreviewer.localImage ?
<LocalImage src={localImagePreviewer.localImage} />
:
<React.Fragment>
<Message>Drag and drop a file to upload...</Message>
<Margin top="1.4vmin" />
<LocalFileSelectButton
theme={BUTTON_THEMES.primary}
handleChange={localImagePreviewer.handleChange}
clearFilePath={localImagePreviewer.clearFilePath}
accept=".png, .jpg, .jpeg"
>
Open file Selector
</LocalFileSelectButton>
</React.Fragment>
}
</DragZone>
</StyledLocalImagePreviewer>
<Margin bottom="3vmin" />
<Button
onClick={localImagePreviewer.removeLocalImage}
isDisabled={!localImagePreviewer.localImage}
theme={BUTTON_THEMES.danger}
>
Delete file
</Button>
</React.Fragment>
);
const App = () => {
const localImagePreviewer = useLocalImagePreviewer(IMAGE_SIZE_LIMIT);
return (
<StyledApp>
<Container>
<LocalImagePreviewer
localImagePreviewer={localImagePreviewer}
height={LOCAL_IMAGE_PREVIEWER.height}
width={LOCAL_IMAGE_PREVIEWER.width}
/>
</Container>
</StyledApp>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
View Compiled
This Pen doesn't use any external CSS resources.