I am currently working on the Body portion for a Spotify clone using ReactJS. I am fairly new to Javascript, HTML, and CSS so apologies for any oversights.
I am having a problem where I cannot scroll down to the bottom of the playlists page even when there are more tracks to be shown. I have tried adding 'overflow-y: scroll' to my CSS which renders a scroll bar but even then, the page cannot be scrolled (see right scrollbar in screenshot below). I have included my Body.jsx below.
Screenshot of Spotify clone
Body.jsx
import React, { useEffect } from 'react'
import styled from 'styled-components';
import {AiFillClockCircle} from "react-icons/ai";
import axios from 'axios';
import { useStateProvider } from '../utils/StateProvider';
import { reducerCases } from "../utils/Constants";
export default function Body() {
const[ {token, selectedPlaylistId, selectedPlaylist} ,dispatch] = useStateProvider();
useEffect(() => {
const getInitialPlaylist = async () => {
const response = await axios.get(
`https://api.spotify.com/v1/playlists/${selectedPlaylistId}`, {
headers: {
Authorization: "Bearer " + token,
"Content-Type": "application/json",
},
}
);
const selectedPlaylist = {
id: response.data.id,
name: response.data.name,
description: response.data.description.startsWith("<a") ? "" : response.data.description,
image: response.data.images[0].url,
tracks: response.data.tracks.items.map(({track}) => ({
id: track.id,
name: track.name,
artists: track.artists.map((artist) => artist.name),
image: track.album.images[2].url,
duration: track.duration_ms,
album: track.album.name,
context_uri: track.album.uri,
track_number: track.track_number,
})),
};
dispatch({type: reducerCases.SET_PLAYLIST,selectedPlaylist});
};
getInitialPlaylist();
},[token,dispatch, selectedPlaylistId]);
return (
<Container>
{
selectedPlaylist && (
<>
<div className="playlist">
<div className="image">
<img src={selectedPlaylist.image} alt="selectedplaylist" />
</div>
<div className="details">
<span className="type">PLAYLIST</span>
<h1 className="title">{selectedPlaylist.name}</h1>
<p className="description">{selectedPlaylist.description}</p>
</div>
</div>
<div className="list">
<div className="header__row">
<div className="col">
<span>#</span>
</div>
<div className="col">
<span>TITLE</span>
</div>
<div className="col">
<span>ALBUM</span>
</div>
<div className="col">
<span><AiFillClockCircle /></span>
</div>
</div>
<div className="tracks">
{
selectedPlaylist.tracks.map(( {id,name,artists,image,duration,album,context_uri,track_number},index) => {
return (
<div className="row" key={id}>
<div className="col">
<span>{index+1}</span>
</div>
<div className="col detail">
<div className="image">
<img src={image} alt="track" />
</div>
<div className="info">
<span className="name">{name}</span>
<span>{artists}</span>
</div>
</div>
<div className="col">
<span>{album}</span>
</div>
<div className="col">
<span>{duration}</span>
</div>
</div>
);
})
}
</div>
</div>
</>
)
}
</Container>
);
}
//CSS for styled components
const Container = styled.div`
overflow-y: scroll; //scroll bar appears but doesn't scroll
.playlist {
margin: 0 2rem;
display: flex;
align-items: center;
gap: 2rem;
.image {
img {
height: 15rem;
box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px;
}
}
.details {
display: flex;
flex-direction: column;
gap: 1rem;
color: #e0dede;
.title {
color: white;
font-size: 4rem;
}
}
}
.list {
.header__row {
display: grid;
grid-template-columns: 0.3fr 3fr 2fr 0.1fr;
margin: 1rem 0 0 0;
color: #dddcdc;
position: sticky;
top: 15vh;
padding: 1rem 3rem;
transition: 0.3s ease-in-out;
}
.tracks {
margin: 0 2rem;
display: flex;
flex-direction: column;
margin-bottom: 5rem;
.row {
padding: 0.5rem 1rem;
display: grid;
grid-template-columns: 0.3fr 3.1fr 2fr 0.1fr;
&:hover {
background-color: rgba(0, 0, 0, 0.7);
}
.col {
display: flex;
align-items: center;
color: #dddcdc;
img {
height: 40px;
width: 40px;
}
}
.detail {
display: flex;
gap: 1rem;
.info {
display: flex;
flex-direction: column;
}
}
}
}
}
`;
If anyone has any ideas as to how I can get the page to be scrollable, I would greatly appreciate it. I have done lots of research but nothing has worked. Thank you so much!
I was working on the same project and faced the same issue.
I figured out that the issue lied in Spotify.jsx file.
Let this be the code:
.body {
height: 100%;
width: 100%;
overflow: auto;
&::-webkit-scrollbar {
width: 0.7rem;
max-height: 2rem;
&-thumb {
background-color:rgba(255, 255, 255, 0.6);
}
}
}
Related
I'm trying to create a flex row split panel with a footer inside a column flexbox. However, I find out that I cannot make the content to be scrollable unless I limit the height of the container by coding height: calc(100% - FooterHeight). Does anyone know an alternative way to achieve this?
Thanks in advance!
Sandbox URL
My code:
import React from "react";
import "./styles.css";
const lefts = new Array(15).fill("Left");
const rights = new Array(15).fill("Right");
export default function App() {
return (
<div className="height">
<div className="column">
<div className="row">
<div className="row-left">
{lefts.map((left, index) => {
return (
<div key={index} className="item">
{left + index}
</div>
);
})}
</div>
<div className="row-right">
{rights.map((right, index) => {
return (
<div key={index} className="item">
{right + index}
</div>
);
})}
</div>
</div>
<div className="footer">Footer</div>
</div>
</div>
);
}
My CSS:
.height {
height: 600px;
width: 600px;
background-color: gray;
display: flex;
flex-direction: column;
}
.column {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
}
.row {
flex: 1;
display: flex;
height: calc(100% - 60px); // not preferred
}
.row-left {
width: 200px;
border: 1px solid red;
overflow: auto;
}
.row-right {
flex: 1;
border: 1px solid peru;
overflow: auto;
}
.item {
padding: 16px;
}
.footer {
flex-shrink: 0;
height: 60px;
border: 1px solid blue;
}
You need to replace that height with overflow: hidden for .row:
.row {
flex: 1;
display: flex;
overflow: hidden;
}
Here is fork of your Codesandbox:
https://codesandbox.io/s/fast-wood-1wxii
Why my damn footer won't stay at the bottom of the page?
import React from "react"
import Navbar from "./navbar"
import Footer from "./footer"
import Sidebar from "./sidebar"
import "./layoutplus.css"
const Layout = ({ children }) => {
return (
<>
<Navbar />
<div className="main-cont">
<main id="main-post" className="main-post">{children}</main>
<Sidebar id="sidebar" className="sidebar"/>
</div>
<Footer />
</>
)
}
export default Layout
import React from 'react'
import {Link} from 'gatsby'
import {FaFacebook, FaTwitter, FaInstagram} from 'react-icons/fa'
export default function Footer() {
return (
<div id="footer" className="footer">
<div className="footer-middle">
<ul>
<li><Link className="footer-link" to="/">Home</Link></li>
<li><Link className="footer-link" to="/guides">Guides</Link></li>
<li><Link className="footer-link" to="/about">About</Link></li>
</ul>
<div className="footer-bottom">
<ul className="footer-ul">
<li><Link to="//" ><FaFacebook className="footer-dot" /></Link></li>
<li><Link to="//"><FaTwitter className="footer-dot" /></Link></li>
<li><Link to="//"><FaInstagram className="footer-dot"/></Link></li>
</ul>
<p>Build with <Link style={{ textDecoration: "none", color: "#663399" }} to="https://www.gatsbyjs.com/">Gatsby</Link>.</p>
<p>©{new Date().getFullYear()} <Link className="footer-link" to="/about">blablabla</Link> - All Rights Reserved</p>
</div>
</div>
</div>
)
}
/*MAIN CONT*/
.main-cont {
max-width: 1344px;
margin: 0 auto;
display: grid;
height: 100vh;
grid-template-columns: 4.5fr 1.5fr;
grid-template-rows: 1fr 1fr;
gap: 1rem;
grid-template-areas:
"main-post test-ads"
"main-post test-hottopic";
padding-top: 40px;
}
#media only screen and (max-width: 800px) {
.main-cont {
grid-template-columns: 1fr;
grid-template-rows: 2fr 1fr 1fr;
grid-template-areas:
"main-post"
"test-ads"
"test-hottopic";
padding-right: 10px;
padding-left: 10px;
}
}
.main-post {
grid-area: main-post;
}
.test-ads {
grid-area: test-ads;
border: 1px solid greenyellow;
text-align: center;
}
.test-hottopic {
grid-area: test-hottopic;
border: 1px solid red;
text-align: center;
}
/*FOOTER*/
.footer {
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
background: var(--bgGray);
margin: 0;
padding-bottom: 20px;
}
.footer-middle {
color: var(--mainWithe);
}
.footer-middle ul {
list-style: none;
color: var(--mainWhite);
padding: 0rem 2px;
}
.footer-middle li {
font-size: 1.2rem;
display: inline;
margin-right: 1.5rem;
}
.footer-bottom {
padding-top: 0.5rem;
padding-bottom: 2rem;
}
.footer-link {
color: var(--offWhite);
text-decoration: none;
}
.footer-link:hover {
color: var(--mainOrange);
}
.footer-ul {
list-style: none;
display: inline;
}
.footer-dot {
color: var(--offWhite);
height: 27px;
width: 27px;
}
.footer-dot:hover {
transition: var(--mainTransition);
color: var(--mainOrange);
}
The footer seems to stay at the bottom of the screen always, but never at the end of the page. I don't know why, this problem starts when I added the grid to the .main-cont container, because I need a grid nested into the body.
Anyone has a solution?
With Gatsby, you should struck the brain a little bit, since it's not that easy to access the wrappers of the content, but of course, it doable.
Taking your <Layout> as an example:
import React from "react"
import Navbar from "./navbar"
import Footer from "./footer"
import Sidebar from "./sidebar"
import "./layoutplus.css"
const Layout = ({ children }) => {
return (
<>
<Navbar />
<div className="main-cont">
<main id="main-post" className="main-post">{children}</main>
<Sidebar id="sidebar" className="sidebar"/>
</div>
<Footer />
</>
)
}
export default Layout
Using the previous structure, you can't access the wrapper of your components because Gatsby wraps it between an unreachable <div> with an empty class. However, what you can do is to wrap it again within a known class element, such as:
const Layout = ({ children }) => {
return (
<section className="site-wrapper">
<Navbar />
<div className="main-cont">
<main id="main-post" className="main-post">{children}</main>
<Sidebar id="sidebar" className="sidebar"/>
</div>
<Footer />
</section>
)
}
Given that site-wrapper class you can add the following CSS:
.site-wrapper {
display: flex;
min-height: 100vh;
flex-direction: column;
}
main {
flex-grow: 1;
}
Basically, you are telling the wrapper (site-wrapper) to expand until filling the viewport (100vh). Since your main tag (change it to the desired class if needed) can grow free (flex-grow: 1) so your footer will be always at the bottom of the page because it's pushed by the rest of the flexbox column.
Flexbox has better acceptance/fallback within old browsers than grid has, but you can achieve it either way, the idea is always to push (or let the main grow) the footer at the bottom without depending on the content above, using some minmax should do the trick using a grid-based approach.
This is the JSX of the component in question:
import React from 'react'
import * as classes from './PhotoCard.module.scss'
const PhotoCard = ({ img, name, username, profileImg, style }) =>
<div className={classes.PhotoCardWrapper} style={style}>
<div className={classes.ImgWrapper}>
{img}
</div>
<div className={classes.Sidebar}>
<div className={classes.Creator}>
<div className={classes.ProfilePicture}>
{profileImg}
</div>
<div className={classes.ProfileInfo}>
<h5>{name}</h5>
<p>{username}</p>
</div>
</div>
<div className={classes.Comments}>
<input type="text" name="comment" id="comment" placeholder="Write a comment" />
</div>
</div>
</div>
export default PhotoCard
Here is the relevant SCSS:
.PhotoCardWrapper {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: flex-start;
box-sizing: border-box;
border-radius: 5px;
box-shadow: 0 2px 6px rgba(0,0,0,0.16), 0 2px 6px rgba(0,0,0,0.2);
.ImgWrapper {
display: flex;
width: 100%;
img {
width: 100%;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
}
.Sidebar {
width: 300px;
height: 100%;
display: flex;
flex-flow: column nowrap;
.Creator {
width: 100%;
display: flex;
padding: 10px;
overflow: hidden;
overflow-x: auto;
box-sizing: border-box;
.ProfilePicture {
width: 40px;
height: 40px;
border-radius: 40px;
background-image: url('../../static/defaults/placeholder.png');
background-size: contain;
cursor: pointer;
margin-right: 10px;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
border-radius: 40px;
}
}
.ProfileInfo {
height: 40px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
}
h5 {
font-weight: 500;
letter-spacing: 0.5px;
}
p {
font-size: 0.7em;
color: $color-2;
}
}
.Comments {
width: 100%;
display: flex;
flex-flow: column nowrap;
flex-grow: 1;
background: #525252;
input {
border: none;
border-top: 1px solid $color-1;
font-size: 0.9em;
padding: 5px;
}
}
}
}
To explain, I'm trying to preserve the aspect ratio of the image on the left side of the component. I can use an absolute unit for the width however I cannot use an absolute height unit as the height of the component will be based upon the aspect ratio of the image.
The "Sidebar" using flexbox will therefore have to match the height of whatever the unspecified height of the image ends up being. I cannot figure out a solution for this. As you can see I'm trying to use flex-grow on the Comments div which will not work without specifying height in parent elements.
This is the solution I found to this:
import React, { useEffect, useState } from 'react'
import * as classes from './_PhotoCard.module.scss'
import PropTypes from 'prop-types'
const PhotoCard = ({ ident, img, name, profileImg }) => {
const [height, setHeight] = useState(null)
const [imgClicked, setImgClicked] = useState(null)
useEffect(() => setHeight(document.getElementsByClassName('_PhotoCard_ImgWrapper__3S6GQ').item(ident).clientHeight), [ident])
const creatorJSX = (
<>
<div className={classes.ProfilePicture}>
{profileImg}
</div>
<div className={classes.ProfileInfo}>
<h5>{name}</h5>
</div>
</>
)
const imgClickedHandler = () => {
if (imgClicked === null) {
setImgClicked(classes.imgClicked)
document.body.style.overflow = "hidden"
} else {
setImgClicked(null)
document.body.style = "none"
}
}
return (
<div className={`${classes.PhotoCardWrapper} ${imgClicked}`} style={window.matchMedia("(min-width: 600px)").matches ? { marginBottom: 40 } : { marginBottom: 20 }}>
<div className={classes.CreatorMobile}>
{creatorJSX}
</div>
<div className={classes.ImgWrapper} onClick={() => imgClickedHandler()}>
{img}
</div>
<div className={classes.Sidebar} style={window.matchMedia("(min-width: 600px)").matches ? { height: height } : null}>
<div className={classes.SidebarTop}>
<div className={classes.Creator}>
{creatorJSX}
</div>
<div className={classes.Comments}/>
</div>
<input type="text" name="comment" id="comment" placeholder="Write a comment" />
</div>
</div>
)
}
PhotoCard.propTypes = {
ident: PropTypes.number,
img: PropTypes.element,
name: PropTypes.string,
profileImg: PropTypes.element,
}
export default PhotoCard
This could only be achieved by implementing some JS witchcraft.
So I have to picture like this example on my web site , and I did this with z-index in the CSS file, I have a problem, when I reduce the browser page the two picture are not stuck together and there is a kind of blank between them. So how can I have tow responsive picture and stuck together when I reduce my browser page?
Vue.component('carousel', {
template: `
<div class="card-carousel" >
<div class="thumbnails">
<div
v-for="(image, index) in images"
:key="image.id"
:class="['thumbnail-image', (activeImage == index) ? 'active' : '']"
#click="activateImage(index)">
<img :src="image.thumb" class="active"/>
</div>
</div>
<div class="containe-carousel">
<span id = "textespan"> {{currentImage.text}}</span>
<div class="photoshop-screenshot">
<img :src="currentImage.big" alt="">
</div>
<div class="card-img">
<img :src="currentImage2.big2" alt="">
</div>
</div>
</div>
`,
computed: {
currentImage() {
return this.images[this.activeImage];
},
currentImage2() {
return this.images[this.activeImage];
}
},
data() {
return {
activeImage: 0,
}
},
methods: {
activateImage(imageIndex) {
this.activeImage = imageIndex;
},
},
props: ['images']
});
.section{
background-color: black;
}
.card-carousel {
user-select: none;
position: relative;
}
.containe-carousel {
padding-top: 5%;
}
.thumbnails {
display: flex;
justify-content: space-evenly;
flex-direction: row;
}
.thumbnail-image {
display: fixed;
align-items: center;
cursor: pointer;
padding: 2px;
}
.thumbnail-image > img {
width: 100%;
height: auto;
transition: all 250ms;
filter: grayscale(100%);
}
.thumbnail-image:selected> img {
box-shadow: 2px 2px 6px 1px rgba(0,0,0, 0.5);
visibility: hidden;
filter: none;
}
.card-img {
position: relative;
}
.card-img > img {
margin: 0 auto;
padding-top: 7%;
z-index: 2;
}
.photoshop-screenshot {
position:absolute;
z-index: 1;
width: 70%;
right:-80px;
bottom:-130px;
}
.active{
filter: sepia(100%) hue-rotate(19deg) saturate(98) brightness(98%) ;
}
#textespan {
text-align: center;
}
.containe-carousel span {
color: white;
font-weight: bold;
}
<section class="section" id="app">
<div class="container">
<div class="text-center" style="margin:0px 50px">
<div class="heading-underscore">
<h2 class="dk-5q-color">
<?php say("X50Q-dashboard-title"); ?>
</h2>
</div>
</div>
<div class="columns">
<div class="column ">
<div class="card-content">
<carousel
:starting-image="0"
:show-progress-bar="true"
:images="images"
></carousel>
</div>
</div>
</div>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<script src ="/x/x50q-rgb-mechanical-keyboard/x50q-cloud-js.js"></script>
<script>
var app = new Vue({
el: '#app',
data() {
return {
images: [
{
text : 'Photoshop',
id: '1',
big: '/images/das-keyboard-x50q/photoshop-profile.PNG',
big2: '/images/das-keyboard-x50q/photoshop-screenshot.png',
thumb: '/images/das-keyboard-x50q/photoshop-logo.jpg'
},
{
text : 'Aurocad',
id: '2',
big: '/images/das-keyboard-x50q/autocad-profile.png',
big2: '/images/das-keyboard-x50q/autocad-screenshot.png',
thumb: '/images/das-keyboard-x50q/autocad-logo.png'
},
{
text : ' Counter-Strike',
id: '3',
big: '/images/das-keyboard-x50q/counterstrike-profile.png',
big2: '/images/das-keyboard-x50q/counterstrike-screenshot.jpg',
thumb: '/images/das-keyboard-x50q/counterstrike-logo.png'
},
{
text : 'League of Legends',
id: '4',
big: '/images/das-keyboard-x50q/leagueoflegends-profile.png',
big2: '/images/das-keyboard-x50q/leagueoflegends-screenshot.png',
thumb: '/images/das-keyboard-x50q/leagueoflegends-logo.jpg'
}
],
}
}
});
</script>
Run code and resize -> expand snippet and resize window size.
See this code:
.container {
position: relative;
display: inline-block;
}
.wrapper {
padding-right: 30%;
}
.wrapper2 {
position: absolute;
bottom: -40%;
width: 100%;
}
.first {
max-width: 100%;
}
.last {
float: right;
max-width: 50%;
}
<div class="container">
<div class="wrapper">
<img src="https://i.stack.imgur.com/JCQ2g.jpg" class="first">
</div>
<div class="wrapper2">
<img src="https://i.stack.imgur.com/yqoSo.jpg" class="last">
</div>
</div>
Like this? Put them in a flex row to control the outer wrapper width that's then responsive. Adjust as needed within.
.wrapper {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 100%;
width: 100%;
background: lightgrey;
}
.a {
flex: 1 0;
height: 200px;
background: orange;
}
.b {
flex: 1 0;
height: 200px;
position: relative;
top: 100px;
left: -100px;
background: pink;
}
<div class="wrapper">
<div class="a">
</div>
<div class="b">
</div>
</div>
I show you here clearest the problem with more details.
and I really want to obtain the first result, when I have a big web page, and when I when I have a small page too. I need these two pictures to be stuck together like the first example for all kind of page size exactly.
I'm quite new in vue and frontend generally. I've created component to show and like/dislike pictures. I planned on using it both on main app view and in modal which shows user liked ones. I use exactly the same code in both cases, but nothing shows on the modal. I can see that the Set with pictures is passed from main app to modal, but the pictures are not visible. Here is my code. It's probably some silly mistake, but i have nobody to ask about it.
Shibe.vue (picture component)
<template>
<div >
<div>
<img :src="shibe">
</div>
<div v-bind:class="{ liked : isFavoured }">
<button type="button" class="btn btn-danger"
#click="addToFavourites(shibe, $emit('liked', shibe))"
>
<font-awesome-icon icon="thumbs-up"/>
</button>
<button type="button" class="btn btn-dark"
#click="removeFromFavourites(shibe, $emit('unliked', shibe))"
style="float: right">
<font-awesome-icon icon="thumbs-down"/>
</button>
</div>
</div>
</template>
<script>
export default {
name: "shibe",
props: {
favoured: {default: false},
shibe: '',
},
data: function () {
return {
isFavoured: false,
modal: false,
}
},
mounted() {
this.isFavoured = this.favoured;
},
methods: {
addToFavourites() {
this.isFavoured = true;
},
removeFromFavourites() {
this.isFavoured = false;
}
},
}
</script>
<style lang="scss">
/* Optional Styles */
.header {
position: fixed;
top: 10px;
right: 10px;
width: 10%;
height: 20px;
text-align: right;
}
.liked {
background: firebrick;
}
</style>
App.vue
<template>
<div id="app">
<nav class="navbar navbar-light bg-light">
<span class="navbar-brand mb-0 h1">
<button type="button" class="btn btn-primary" #click="showModal()">
Favourites
</button>
</span>
</nav>
<div class="doge" v-for="shibe in shibes">
<shibe :shibe=shibe #liked="addToFavourited" #unliked="removeFromFavourited"></shibe>
</div>
<favs :favourites=favouriteShibes
v-show="isModalVisible"
#close="closeModal"></favs>
</div>
</template>
<script>
import axios from 'axios';
import 'bootstrap/dist/css/bootstrap.css';
import Favs from "./Favs";
import Shibe from "./Shibe";
export default {
name: 'app',
components: {Favs, Shibe},
data() {
return {
shibes: new Set(),
favouriteShibes: new Set(),
isModalVisible: false,
}
},
methods: {
getInitialShibes() {
axios.get(`http://shibe.online/api/shibes?count=12`)
.then(response => {
this.shibes = response.data;
});
},
addToFavourited(shibe) {
this.favouriteShibes.add(shibe);
console.log(this.favouriteShibes);
},
removeFromFavourited(shibe) {
this.favouriteShibes.delete(shibe);
console.log(this.favouriteShibes);
},
scroll() {
window.onscroll = () => {
let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight === document.documentElement.offsetHeight;
if (bottomOfWindow) {
for (let i = 0; i < 4; i++) {
axios.get(`http://shibe.online/api/shibes?count=1`)
.then(response => {
this.shibes.push(response.data);
});
}
}
}
},
showModal() {
this.isModalVisible = true;
},
closeModal() {
this.isModalVisible = false;
}
},
mounted() {
this.scroll();
},
beforeMount() {
this.getInitialShibes();
}
}
</script>
<style lang="scss">
.header {
position: fixed;
top: 10px;
right: 10px;
width: 10%;
height: 20px;
text-align: right;
}
.doge {
border-radius: 2px;
width: 25%;
height: 20%;
margin: 0 auto 15px auto;
padding: 15px;
display: inline-block;
flex-wrap: wrap;
img {
width: 100%;
height: 100%;
border-radius: 2px;
}
}
.doge-div {
width: 25%;
align-items: center;
}
</style>
Favs.vue (Modal component)
<template>
<div class="modal-backdrop">
<div class="modal">
<header class="modal-header">
<slot name="header">
<button #click="console()">Favourite shibes
</button>
<button
type="button"
class="btn-close"
#click="close"
>
x
</button>
</slot>
</header>
<section class="modal-body">
<slot>
<div class="doge" v-for="shibe in favourites">
<shibe :shibe=shibe></shibe>
</div>
</slot>
</section>
</div>
</div>
</template>
<script>
import Shibe from "./Shibe";
export default {
name: "favs",
components: {Shibe},
props: {
favourites: {},
},
methods: {
close() {
this.$emit('close');
},
console() {
console.log(this.favourites);
}
},
}
</script>
<style lang="scss">
.modal-backdrop {
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: #FFFFFF;
box-shadow: 2px 2px 20px 1px;
overflow-x: auto;
display: flex;
flex-direction: column;
width: 80%;
height: 60%;
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -30%);
}
.modal-header,
.modal-footer {
padding: 15px;
display: flex;
}
.modal-header {
border-bottom: 1px solid #eeeeee;
color: #4AAE9B;
justify-content: space-between;
}
.modal-body {
position: relative;
padding: 20px 10px;
}
.btn-close {
border: none;
font-size: 20px;
padding: 20px;
cursor: pointer;
font-weight: bold;
color: #4AAE9B;
background: transparent;
}
</style>