generated from goitacademy/react-homework-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6c8bfbc
commit e17b0c9
Showing
17 changed files
with
1,178 additions
and
60 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import axios from 'axios'; | ||
|
||
const BASE_URL = 'https://pixabay.com/api/'; | ||
const API_KEY = '40728648-1b398202c5fa26f14327021d8'; | ||
|
||
const getImages = async (search, page) => { | ||
const response = await axios.get(BASE_URL, { | ||
method: 'get', | ||
params: { | ||
key: API_KEY, | ||
q: search, | ||
image_type: 'photo', | ||
orientation: 'horizontal', | ||
per_page: 12, | ||
page: page, | ||
}, | ||
}); | ||
console.log(response.data); | ||
console.log(response.data.hits); | ||
return response; | ||
}; | ||
|
||
export default getImages; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,184 @@ | ||
export const App = () => { | ||
return ( | ||
<div | ||
style={{ | ||
height: '100vh', | ||
display: 'flex', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
fontSize: 40, | ||
color: '#010101', | ||
}} | ||
> | ||
React homework template!!!! | ||
</div> | ||
); | ||
}; | ||
import React, { Component } from 'react'; | ||
import Notify from 'notiflix/build/notiflix-notify-aio'; | ||
import getImages from './API/Api'; | ||
import Searchbar from './Searchbar/Searchbar'; | ||
import ImageGallery from './ImageGallery/ImageGallery'; | ||
import Button from './Button/Button'; | ||
import Modal from './Modal/Modal'; | ||
import Loader from './Loader/Loader'; | ||
|
||
export default class App extends Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
images: [], | ||
query: '', | ||
page: 1, | ||
loading: false, | ||
openModal: false, | ||
largeImageURL: '', | ||
loadMore: false, | ||
toTop: false, | ||
}; | ||
|
||
// Ref for scrolling to bottom | ||
this.bottomRef = React.createRef(); | ||
} | ||
|
||
// Scroll to bottom with load more | ||
scrollToBottom = () => { | ||
this.bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); | ||
}; | ||
|
||
handleSubmit = query => { | ||
this.setState({ | ||
loading: true, | ||
images: [], | ||
query: query, | ||
page: 1, | ||
}); | ||
}; | ||
|
||
handleOpenModal = url => { | ||
this.setState({ | ||
openModal: true, | ||
largeImageURL: url, | ||
}); | ||
}; | ||
|
||
handleLoadMore = () => { | ||
this.setState(prevState => ({ | ||
page: prevState.page + 1, | ||
loading: true, | ||
loadMore: true, | ||
toTop: true, | ||
})); | ||
}; | ||
|
||
backToTop = () => { | ||
window.scrollTo({ | ||
left: 0, | ||
top: 0, | ||
behavior: 'smooth', | ||
}); | ||
}; | ||
|
||
handleModalClose = () => { | ||
this.setState({ | ||
openModal: false, | ||
largeImageURL: '', | ||
}); | ||
}; | ||
|
||
componentDidMount() { | ||
this.getGallery(); | ||
} | ||
|
||
componentDidUpdate(prevProps, prevState) { | ||
if ( | ||
prevState.query !== this.state.query || | ||
prevState.page !== this.state.page | ||
) { | ||
this.getGallery(); | ||
} | ||
} | ||
|
||
getGallery = async () => { | ||
const { query, page } = this.state; | ||
|
||
if (!query) return; | ||
|
||
try { | ||
const response = await getImages(query, page); | ||
let imageData = response.data; | ||
let imageCount = imageData.hits.length; | ||
let imageTotal = imageData.totalHits; | ||
let totalPages = Math.round(imageTotal / imageCount); | ||
|
||
if (imageCount === 0) { | ||
this.setState({ | ||
images: [], | ||
loadMore: false, | ||
toTop: false, | ||
}); | ||
Notify.failure( | ||
`Sorry, there are no images matching your search query: ${query}. Please try again.` | ||
); | ||
return; | ||
} | ||
|
||
const newState = { images: [...this.state.images, ...imageData.hits] }; | ||
|
||
if (imageCount < 12) { | ||
this.setState({ ...newState, toTop: true }); | ||
if (page === 1) { | ||
this.setState({ | ||
loadMore: false, | ||
toTop: false, | ||
}); | ||
Notify.success( | ||
`Maximum search value found, there are ${imageCount} images.` | ||
); | ||
} | ||
} else { | ||
this.setState({ | ||
...newState, | ||
loadMore: page !== totalPages, | ||
}); | ||
if (page >= 2 && page <= 41) { | ||
this.setState({ | ||
loadMore: true, | ||
toTop: true, | ||
}); | ||
} else if (page === 42) { | ||
this.setState({ | ||
toTop: true, | ||
loadMore: false, | ||
}); | ||
} else if (imageTotal > 12) { | ||
this.setState({ | ||
loading: true, | ||
loadMore: true, | ||
toTop: true, | ||
}); | ||
} | ||
} | ||
} finally { | ||
this.setState({ | ||
loading: false, | ||
}); | ||
} | ||
}; | ||
|
||
render() { | ||
const { images, loading, loadMore, toTop, openModal, largeImageURL } = | ||
this.state; | ||
|
||
return ( | ||
<div> | ||
<Searchbar onSubmit={this.handleSubmit} /> | ||
|
||
<ImageGallery | ||
images={images} | ||
openModal={this.handleOpenModal} | ||
loadMore={this.handleLoadMore} | ||
/> | ||
<div ref={this.bottomRef}></div> | ||
{''} | ||
{loading && <Loader />} | ||
<div className="Center-buttons"> | ||
{loadMore && ( | ||
<Button clickHandler={this.handleLoadMore} text="Load More" /> | ||
)} | ||
{toTop && <Button clickHandler={this.backToTop} text="To Top" />} | ||
</div> | ||
{openModal && ( | ||
<Modal | ||
largeImageURL={largeImageURL} | ||
modalClose={this.handleModalClose} | ||
/> | ||
)} | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { string, func } from 'prop-types'; | ||
import './Button.modules.css'; | ||
|
||
const Button = props => { | ||
const { text, type, clickHandler } = props; | ||
|
||
return ( | ||
<div className="Center-buttons"> | ||
<button | ||
className="Button" | ||
type={type} | ||
onClick={clickHandler} | ||
aria-label={text} | ||
> | ||
{text} | ||
</button> | ||
</div> | ||
); | ||
}; | ||
|
||
Button.defaultProps = { | ||
type: 'button', | ||
text: 'click me', | ||
}; | ||
|
||
Button.propTypes = { | ||
type: string, | ||
text: string, | ||
}; | ||
|
||
export default Button; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
.Button { | ||
padding: 8px 16px; | ||
border-radius: 2px; | ||
background-color: #3f51b5; | ||
transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1); | ||
text-align: center; | ||
display: inline-block; | ||
color: #fff; | ||
border: 0; | ||
text-decoration: none; | ||
cursor: pointer; | ||
font-family: inherit; | ||
font-size: 18px; | ||
line-height: 24px; | ||
font-style: normal; | ||
font-weight: 500; | ||
min-width: 180px; | ||
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), | ||
0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); | ||
} | ||
|
||
.Button:hover, | ||
.Button:focus { | ||
background-color: #303f9f; | ||
} | ||
|
||
.Center-buttons { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { array, func } from 'prop-types'; | ||
import ImageGalleryItem from 'components/ImageGalleryItem/ImageGalleryItem'; | ||
import './ImageGallery.modules.css'; | ||
|
||
const ImageGallery = props => { | ||
const { images, openModal } = props; | ||
return ( | ||
<ul className="ImageGallery"> | ||
{images.map(({ id, webformatURL, largeImageURL, tags }) => ( | ||
<ImageGalleryItem | ||
key={id} | ||
webformatURL={webformatURL} | ||
largeImageURL={largeImageURL} | ||
openModal={openModal} | ||
tags={tags} | ||
/> | ||
))} | ||
</ul> | ||
); | ||
}; | ||
|
||
ImageGallery.propTypes = { | ||
images: array.isRequired, | ||
openModal: func.isRequired, | ||
}; | ||
|
||
export default ImageGallery; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
.ImageGallery { | ||
display: grid; | ||
max-width: calc(100vw - 48px); | ||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); | ||
grid-gap: 16px; | ||
margin-top: 0; | ||
margin-bottom: 0; | ||
padding: 0; | ||
list-style: none; | ||
margin-left: auto; | ||
margin-right: auto; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { string, func } from 'prop-types'; | ||
import './ImageGalleryItem.modules.css'; | ||
|
||
const ImageGalleryItem = props => { | ||
const { webformatURL, tags, largeImageURL, openModal } = props; | ||
|
||
return ( | ||
<li> | ||
<img | ||
className="ImageGalleryItem-image" | ||
src={webformatURL} | ||
alt={tags} | ||
largeImage={largeImageURL} | ||
onClick={() => openModal(largeImageURL)} | ||
/> | ||
</li> | ||
); | ||
}; | ||
|
||
ImageGalleryItem.defaultProps = { | ||
largeImageURL: 'https://picsum.photos/100%/260', | ||
webformatURL: 'https://picsum.photos/100%/260', | ||
}; | ||
|
||
ImageGalleryItem.propTypes = { | ||
largeImageURL: string, | ||
webformatURL: string, | ||
tags: string.isRequired, | ||
openModal: func.isRequired, | ||
}; | ||
|
||
export default ImageGalleryItem; |
17 changes: 17 additions & 0 deletions
17
src/components/ImageGalleryItem/ImageGalleryItem.modules.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
.ImageGalleryItem { | ||
border-radius: 2px; | ||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), | ||
0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12); | ||
} | ||
|
||
.ImageGalleryItem-image { | ||
width: 100%; | ||
height: 260px; | ||
object-fit: cover; | ||
transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1); | ||
} | ||
|
||
.ImageGalleryItem-image:hover { | ||
transform: scale(1.03); | ||
cursor: zoom-in; | ||
} |
Oops, something went wrong.