Live example (images might load slowly): https://suhadolnik-photo.surge.sh/portreti
I'm making a photography site with GatsbyJS and using the following template as a base site that I've been changing: https://github.com/LekoArts/gatsby-starter-portfolio-emilia
Being really new to graphql I've run into a problem displaying images after a user clicks on the card to show the 'Portraits' subpage. The images are all displayed with a fixed width and height which I don't want. I need to display them with their native width and height, just resized to fit into the grid.
I've tried changing the graphql query in the project.js file, where you set the maxWidth: 1600 to no avail, as well as the resize(width: 800) further down the query. Later I found out that changing the margin on gatsby-image-wrapper through dev tools gave me the expected results, but that required changing the core gatsby-image plugin and having to manually change the margin for every image separately which isn't the solution.
project.js
import React from 'react'
import Img from 'gatsby-image'
import PropTypes from 'prop-types'
import { graphql } from 'gatsby'
import styled from 'styled-components'
import { Layout, ProjectHeader, ProjectPagination, SEO } from '../components'
import config from '../../config/site'
const BG = styled.div`
background-color: ${props => props.theme.colors.bg};
position: relative;
padding: 2rem 0 0 0;
`
const OuterWrapper = styled.div`
padding: 0 ${props => props.theme.contentPadding};
margin: -10rem auto 0 auto;
`
const InnerWrapper = styled.div`
position: relative;
max-width: ${props => `${props.theme.maxWidths.project}px`};
margin: 0 auto;
`
const Grid = styled.div`
display: grid;
grid-template-columns: repeat(${props => props.theme.gridColumnsProject}, 1fr);
grid-gap: 20px;
#media (max-width: 768px) {
grid-template-columns: 1fr;
}
`
const Project = ({ pageContext: { slug, prev, next }, data: { project: postNode, images } }) => {
const project = postNode.frontmatter
return (
<Layout customSEO>
<SEO postPath={slug} postNode={postNode} postSEO />
<ProjectHeader
name={config.name}
date={project.date}
title={project.title}
areas={project.areas}
text={postNode.body}
/>
<BG>
<OuterWrapper>
<InnerWrapper>
<Grid>
{images.nodes.map(image => (
<Img
alt={image.name}
key={image.childImageSharp.fluid.src}
fluid={image.childImageSharp.fluid}
style={{ margin: '2rem 0' }}
/>
))}
</Grid>
</InnerWrapper>
<ProjectPagination next={next} prev={prev} />
</OuterWrapper>
</BG>
</Layout>
)
}
export default Project
Project.propTypes = {
pageContext: PropTypes.shape({
slug: PropTypes.string.isRequired,
next: PropTypes.object,
prev: PropTypes.object,
}),
data: PropTypes.shape({
project: PropTypes.object.isRequired,
images: PropTypes.object.isRequired,
}).isRequired,
}
Project.defaultProps = {
pageContext: PropTypes.shape({
next: null,
prev: null,
}),
}
export const pageQuery = graphql`
query($slug: String!, $absolutePathRegex: String!) {
images: allFile(
filter: {
absolutePath: { regex: $absolutePathRegex }
extension: { regex: "/(jpg)|(png)|(tif)|(tiff)|(webp)|(jpeg)/" }
}
sort: { fields: name, order: ASC }
) {
nodes {
name
childImageSharp {
fluid(maxWidth: 1600, quality: 90) {
...GatsbyImageSharpFluid_withWebp
}
}
}
}
project: mdx(fields: { slug: { eq: $slug } }) {
body
excerpt
parent {
... on File {
mtime
birthtime
}
}
frontmatter {
cover {
childImageSharp {
resize(width: 800) {
src
}
}
}
date(formatString: "DD.MM.YYYY")
title
areas
}
}
}
`
Card.js the parent component:
import React from 'react'
import styled from 'styled-components'
import PropTypes from 'prop-types'
import { useSpring, animated, config } from 'react-spring'
import { rgba } from 'polished'
import Img from 'gatsby-image'
import { Link } from 'gatsby'
const CardItem = styled(Link)`
min-height: 500px;
position: relative;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
justify-content: flex-end;
color: ${props => props.theme.colors.color};
transition: all 0.3s ease-in-out;
&:hover {
color: white;
transform: translateY(-6px);
}
#media (max-width: ${props => props.theme.breakpoints.s}) {
min-height: 300px;
}
`
const Cover = styled.div`
width: 100%;
height: 100%;
position: absolute;
`
const Content = styled.div`
padding: 1rem;
position: relative;
transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
opacity: 0;
background: ${props => rgba(props.theme.colors.link, 0.65)};
height: 0;
${CardItem}:hover & {
opacity: 1;
height: 120px;
}
`
const Bottom = styled.div`
margin-top: 0.5rem;
display: flex;
align-items: center;
font-size: 0.85rem;
div:first-child {
margin-right: 1rem;
}
`
const Name = styled.h2`
margin-bottom: 0;
margin-top: 0;
`
const Card = ({ path, cover, date, areas, title, delay }) => {
const springProps = useSpring({
config: config.slow,
delay: 200 * delay,
from: { opacity: 0, transform: 'translate3d(0, 30px, 0)' },
to: { opacity: 1, transform: 'translate3d(0, 0, 0)' },
})
return (
<animated.div style={springProps}>
<CardItem to={path}>
<Cover>
<Img fluid={cover} />
</Cover>
<Content>
<Name>{title}</Name>
<Bottom>
<div>{date}</div>
<div>
{areas.map((area, index) => (
<React.Fragment key={area}>
{index > 0 && ', '}
{area}
</React.Fragment>
))}
</div>
</Bottom>
</Content>
</CardItem>
</animated.div>
)
}
export default Card
Card.propTypes = {
path: PropTypes.string.isRequired,
cover: PropTypes.object.isRequired,
date: PropTypes.string.isRequired,
areas: PropTypes.array.isRequired,
title: PropTypes.string.isRequired,
delay: PropTypes.number.isRequired,
}
I expect the images to show in their native width and height, but resized to fit the grid. Providing visual representation below on how it looks now and what the expected result is.
Current result and expected result
Cheers!
Remove height:100% and position:absolute from your cover component on the homepage.
const Cover = styled.div`
width: 100%;
`
Also, in case you weren't aware, you can pass style and imgStyle props to Gatsby image to change it's css.
| style | object | Spread into the default styles of the wrapper element |
| imgStyle | object | Spread into the default styles of the actual img element |
| placeholderStyle | object | Spread into the default styles of the placeholder img element |
So in your project template you can change the object fit style like this:
<Img
alt={image.name}
key={image.childImageSharp.fluid.src}
fluid={image.childImageSharp.fluid}
imgStyle={{ objectFit: 'contain' }}
/>
Related
I get errors in the console about the lack of the required props (height / width). How can I find out where to specify them (in what file and what should the class be called)?
I am aware that could be a dummy question so I also would be grateful if you would reccomend some viedo/tutorial in your opinion useful to get to know more devtools basics.
This is my first question, so I'm not sure what information I should provide. Let me know if anything is missing.
My console 1
Home.vue
<template>
<div id="home">
<LazyHydrate when-idle>
<SfHero class="hero">
<SfHeroItem
v-for="(hero, i) in heroes"
:key="i"
:title="$t(hero.title)"
:subtitle="$t(hero.subtitle)"
:background="hero.background"
:image="hero.image | addBasePathFilter"
:class="hero.className"
:height="200"
:width="200"
/>
</SfHero>
</LazyHydrate>
<LazyHydrate when-visible>
<SfBannerGrid :banner-grid="1" class="banner-grid">
<template v-for="item in banners" v-slot:[item.slot]>
<SfBanner
:key="item.slot"
:title="$t(item.title)"
:subtitle="$t(item.subtitle)"
:description="$t(item.description)"
:button-text="$t(item.buttonText)"
:link="localePath(item.link)"
:image="item.image | addBasePathFilter"
:class="item.class"
:height="200"
:width="200"
/>
</template>
</SfBannerGrid>
</LazyHydrate>
<LazyHydrate when-visible>
<div class="similar-products">
<SfHeading :title="$t('Match with it')" :level="2" />
<nuxt-link :to="localePath('/c/women')" class="smartphone-only">See all</nuxt-link>
</div>
</LazyHydrate>
<LazyHydrate when-visible>
<SfCarousel
class="carousel"
:settings="{ peek: 16, breakpoints: { 1023: { peek: 0, perView: 2 } } }"
>
<template #prev="{go}">
<SfArrow
aria-label="prev"
class="sf-arrow--left sf-arrow--long"
#click="go('prev')"
/>
</template>
<template #next="{go}">
<SfArrow
aria-label="next"
class="sf-arrow--right sf-arrow--long"
#click="go('next')"
/>
</template>
<SfCarouselItem
class="carousel__item"
v-for="(product, i) in products"
:key="i"
>
<SfProductCard
class="carousel__item__product"
:title="product._name"
:image="productGetters.getCoverImage(product) | addBasePathFilter"
image-tag="nuxt-img"
:nuxt-img-config="{
format: 'webp',
fit: 'fill'
}"
:image-width="216"
:image-height="290"
:regular-price="productPriceTransform(product).regular"
:special-price="productPriceTransform(product).special"
:is-added-to-cart="isInCart({ product })"
:is-in-wishlist="isInWishlist({ product })"
show-add-to-cart-button
:link="localePath(`/p/${productGetters.getSlug(product)}/${productGetters.getSku(product)}`)"
#click:add-to-cart="addToCart({ product, quantity: 1 })"
#click:wishlist="!isInWishlist({ product }) ? addProductToWishlist(product) : removeProductFromWishlist(product)"
/>
</SfCarouselItem>
</SfCarousel>
</LazyHydrate>
<LazyHydrate when-visible>
<SfCallToAction
:title="$t('Subscribe to Newsletters')"
:button-text="$t('Subscribe')"
:description="$t('Be aware of upcoming sales and events. Receive gifts and special offers!')"
:image="'/homepage/newsletter.webp' | addBasePathFilter"
class="call-to-action"
>
<template #button>
<SfButton
class="sf-call-to-action__button"
data-testid="cta-button"
#click="handleNewsletterClick"
>
{{ $t('Subscribe') }}
</SfButton>
</template>
</SfCallToAction>
</LazyHydrate>
<LazyHydrate when-visible>
<NewsletterModal #email-submitted="onSubscribe" />
</LazyHydrate>
</div>
</template>
<script>
import {
SfHero,
SfBanner,
SfCallToAction,
SfCarousel,
SfProductCard,
SfBannerGrid,
SfHeading,
SfArrow,
SfButton
} from '#storefront-ui/vue';
import LazyHydrate from 'vue-lazy-hydration';
import { ref, computed, watch, useContext } from '#nuxtjs/composition-api';
import { onSSR } from '#vue-storefront/core';
import {
useCart,
useFacet,
useWishlist,
useCurrency,
facetGetters,
productGetters,
wishlistGetters,
productPriceTransform
} from '#vsf-enterprise/commercetools';
import NewsletterModal from '~/components/NewsletterModal.vue';
import { useUiState, useUiNotification } from '../composables';
export default {
name: 'Home',
setup() {
const { app: { i18n } } = useContext();
const { toggleNewsletterModal } = useUiState();
const { send } = useUiNotification();
const {
isInCart,
addItem: addItemToCart,
error: cartError
} = useCart();
const { result, search } = useFacet('home');
const { currency } = useCurrency();
const { addItem: addItemToWishlist, isInWishlist, removeItem: removeItemFromWishlist, wishlist, error: wishlistError } = useWishlist();
const products = computed(() => facetGetters.getProducts(result.value));
const fetchProducts = async () => {
await search({
filters: {},
page: 1,
itemsPerPage: 12,
sort: 'latest',
phrase: ''
});
};
watch(currency, async () => {
await fetchProducts();
});
onSSR(async () => {
await fetchProducts();
});
const mocks = {
heroes: [
{
title: 'Colorful summer dresses are already in store',
subtitle: 'SUMMER COLLECTION 2022',
background: '#eceff1',
image: '/homepage/bannerH.webp'
},
{
title: 'Colorful summer dresses are already in store',
subtitle: 'SUMMER COLLECTION 2022',
background: '#efebe9',
image: '/homepage/bannerA.webp',
className:
'sf-hero-item--position-bg-top-left sf-hero-item--align-right'
},
{
title: 'Colorful summer dresses are already in store',
subtitle: 'SUMMER COLLECTION 2022',
background: '#fce4ec',
image: '/homepage/bannerB.webp'
}
],
banners: [
{
slot: 'banner-A',
subtitle: 'Dresses',
title: 'Cocktail & Party',
description: 'Find stunning women\'s cocktail dresses and party dresses. Stand out in lace and metallic cocktail dresses from all your favorite brands.',
buttonText: 'Shop now',
image: '/homepage/bannerF.webp',
class: 'sf-banner--slim desktop-only',
link: '/c/women/women-clothing-skirts'
},
{
slot: 'banner-B',
subtitle: 'Dresses',
title: 'Linen Dresses',
description: 'Find stunning women\'s cocktail dresses and party dresses. Stand out in lace and metallic cocktail dresses from all your favorite brands.',
buttonText: 'Shop now',
image: '/homepage/bannerE.webp',
class: 'sf-banner--slim banner-central desktop-only',
link: '/c/women/women-clothing-dresses'
},
{
slot: 'banner-C',
subtitle: 'T-Shirts',
title: 'The Office Life',
image: '/homepage/bannerC.webp',
class: 'sf-banner--slim banner__tshirt',
link: '/c/women/women-clothing-shirts'
},
{
slot: 'banner-D',
subtitle: 'Summer Sandals',
title: 'Eco Sandals',
image: '/homepage/bannerG.webp',
class: 'sf-banner--slim',
link: '/c/women/women-shoes-sandals'
}
]
};
const heroes = ref(mocks.heroes);
const banners = ref(mocks.banners);
const handleNewsletterClick = () => {
toggleNewsletterModal();
};
const onSubscribe = (emailAddress) => {
console.log(`Email ${emailAddress} was added to newsletter.`);
toggleNewsletterModal();
};
const addToCart = async ({ product, quantity }) => {
const { id, sku } = product;
await addItemToCart({
product: { id, sku },
quantity
});
if (!cartError.value.addItem) {
send({
type: 'success',
message: i18n.t('Product has been added to the cart.')
});
}
};
const addProductToWishlist = async (product) => {
await addItemToWishlist({ product });
if (!wishlistError.value.addItem) {
send({
type: 'success',
message: i18n.t('Product has been added to the wishlist.')
});
}
};
const removeProductFromWishlist = async (productItem) => {
const productsInWhishlist = computed(() => wishlistGetters.getItems(wishlist.value));
const product = productsInWhishlist.value.find(wishlistProduct => wishlistProduct.variant.sku === productItem.sku);
await removeItemFromWishlist({ product });
if (!wishlistError.value.removeItem) {
send({
type: 'success',
message: i18n.t('Product has been removed from the wishlist.')
});
}
};
return {
heroes,
banners,
products,
productGetters,
handleNewsletterClick,
onSubscribe,
isInCart,
addToCart,
addProductToWishlist,
isInWishlist,
removeProductFromWishlist,
productPriceTransform
};
},
components: {
LazyHydrate,
NewsletterModal,
SfArrow,
SfBanner,
SfBannerGrid,
SfButton,
SfCallToAction,
SfCarousel,
SfHeading,
SfHero,
SfProductCard
},
beforeRouteEnter (_, _2, next) { next('/home-page') }
};
</script>
<style lang="scss">
.carousel__item__product {
.sf-product-card__title {
margin: var(--spacer-base) 0 var(--spacer-xs) 0;
}
.sf-product-card__add-button {
margin-bottom: var(--spacer-xl);
}
}
</style>
<style lang="scss" scoped>
#home {
box-sizing: border-box;
padding: 0 var(--spacer-sm);
#include for-desktop {
max-width: 1240px;
padding: 0;
margin: 0 auto;
}
}
.hero {
margin: var(--spacer-xl) auto var(--spacer-lg);
--hero-item-background-position: center;
#include for-desktop {
margin: var(--spacer-xl) auto var(--spacer-2xl);
}
.sf-hero-item {
min-height: 230px;
&:nth-child(even) {
--hero-item-background-position: left;
#include for-mobile {
--hero-item-background-position: 30%;
::v-deep .sf-hero-item__subtitle,
::v-deep .sf-hero-item__title {
text-align: right;
width: 100%;
padding-left: var(--spacer-sm);
}
}
}
}
::v-deep .sf-hero__control {
&--right,
&--left {
display: none;
}
}
}
.banner-grid {
--banner-container-width: 50%;
margin: var(--spacer-xl) 0;
::v-deep .sf-link:hover {
color: var(--c-white);
}
#include for-desktop {
margin: var(--spacer-2xl) 0;
::v-deep .sf-link {
--button-width: auto;
text-decoration: none;
}
}
}
.banner {
&__tshirt {
background-position: left;
}
&-central {
#include for-desktop {
--banner-container-flex: 0 0 70%;
}
}
}
.similar-products {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: var(--spacer-2xs);
--heading-padding: 0;
border-bottom: 1px var(--c-light) solid;
#include for-desktop {
border-bottom: 0;
justify-content: center;
padding-bottom: 0;
}
}
.call-to-action {
background-position: right;
margin: var(--spacer-xs) 0;
#include for-desktop {
margin: var(--spacer-xl) 0 var(--spacer-2xl) 0;
}
}
.carousel {
margin: 0 calc(0 - var(--spacer-sm)) 0 0;
#include for-desktop {
margin: 0;
}
&__item {
margin: 1.375rem 0 2.5rem 0;
#include for-desktop {
margin: var(--spacer-xl) 0 var(--spacer-xl) 0;
}
&__product {
--product-card-add-button-transform: translate3d(0, 30%, 0);
::v-deep .sf-product-card {
&__title {
margin: var(--spacer-base) 0 var(--spacer-xs) 0;
}
&__add-button {
margin-bottom: var(--spacer-xl);
}
}
}
}
::v-deep .sf-arrow--long .sf-arrow--right {
--arrow-icon-transform: rotate(180deg);
-webkit-transform-origin: center;
transform-origin: center;
}
}
</style>
default.vue
<template>
<div>
<RenderContent v-if="styleGuide.length" :content="styleGuide" />
<LazyHydrate when-visible>
<TopBar class="desktop-only" />
</LazyHydrate>
<LazyHydrate when-idle>
<AppHeader />
</LazyHydrate>
<div id="layout">
<nuxt :key="$route.fullPath" />
<LazyHydrate when-visible>
<BottomNavigation />
</LazyHydrate>
<CartSidebar />
<WishlistSidebar />
<FiltersSidebar />
<LoginModal />
<Notification />
</div>
<LazyHydrate when-visible>
<AppFooter />
</LazyHydrate>
</div>
</template>
<script>
import AppHeader from '~/components/AppHeader.vue'
import BottomNavigation from '~/components/BottomNavigation.vue'
import AppFooter from '~/components/AppFooter.vue'
import TopBar from '~/components/TopBar.vue'
import CartSidebar from '~/components/CartSidebar.vue'
import WishlistSidebar from '~/components/WishlistSidebar.vue'
import FiltersSidebar from '~/components/FiltersSidebar.vue'
import LoginModal from '~/components/LoginModal.vue'
import Notification from '~/components/Notification'
import useCmsLayout from '~/composables/useCmsLayout'
import { onMounted } from '#vue/composition-api'
import LazyHydrate from 'vue-lazy-hydration'
import { useStore, useUser, useWishlist } from '#vsf-enterprise/commercetools'
import { onSSR } from '#vue-storefront/core'
export default {
name: 'DefaultLayout',
components: {
LazyHydrate,
TopBar,
AppHeader,
BottomNavigation,
AppFooter,
CartSidebar,
WishlistSidebar,
FiltersSidebar,
LoginModal,
Notification,
},
setup() {
const { load: loadStores } = useStore()
const { load: loadUser } = useUser()
const { load: loadWishlist } = useWishlist()
const { getLayout, styleGuide } = useCmsLayout()
onSSR(async () => {
await Promise.all([loadStores(), getLayout()])
})
onMounted(async () => {
await Promise.all([loadUser(), loadWishlist()])
})
return {
styleGuide,
}
},
head() {
return this.$nuxtI18nHead({ addSeoAttributes: true })
},
}
</script>
<style lang="scss">
#import '~#storefront-ui/vue/styles';
#layout {
box-sizing: border-box;
#include for-desktop {
max-width: 1240px;
margin: auto;
}
}
.no-scroll {
overflow: hidden;
height: 100vh;
}
// Reset CSS
html {
width: auto;
#include for-mobile {
overflow-x: hidden;
}
}
body {
overflow-x: hidden;
color: var(--c-text);
font-size: var(--font-size--base);
font-family: var(--font-family--primary);
margin: 0;
padding: 0;
}
a {
text-decoration: none;
color: var(--c-link);
&:hover {
color: var(--c-link-hover);
}
}
h1 {
font-family: var(--font-family--secondary);
font-size: var(--h1-font-size);
line-height: 1.6;
margin: 0;
}
h2 {
font-family: var(--font-family--secondary);
font-size: var(--h2-font-size);
line-height: 1.6;
margin: 0;
}
h3 {
font-family: var(--font-family--secondary);
font-size: var(--h3-font-size);
line-height: 1.6;
margin: 0;
}
h4 {
font-family: var(--font-family--secondary);
font-size: var(--h4-font-size);
line-height: 1.6;
margin: 0;
}
</style>
Welcome on stackoverflow!
This error is triggered when a Vue component declares "props" as required, and they are not provided.
The logs says:
Missing required prop height. ------------------- SfImage.vue
So this error is trigger from your SfImage.vue component.
Hence I guess you declared the height and width props from this component as required.
Solution:
You remove the required: true option of your props
OR
You correctly provide these props when you use that component: <SfImage height="40px" width="40px" />
The error in the Chrome Dev Tools usually tells you the name of the component you used without providing the required props. In a vue component, props are declared inside it, under the props property (if using vue2), or under declareProps macro (if using vue3)
For more info, you can read about vue component props in the official documentation: https://vuejs.org/guide/components/props.html
I am using create-react-app, and im using a Data.js file where i have object with properties which i spread as props in a tag. But when i run npm start, or deploy my image wont show, and it looks like the compiler puts my video and images in a static/media directory. i tried webpack, file-loader en url-loader but no luck.
home.js
import React, { useState } from 'react'
import Sidebar from '../components/SideBar'
import NavBar from '../components/NavBar'
import HeroSection from '../components/HeroSection'
import InfoSection from '../components/InfoSection'
import { homeObjOne, homeObjTwo, homeObjThree } from '../components/InfoSection/Data'
//set sidebar navbar toggle states
//default state toggle is
const Home = () => {
const [isOpen, setIsOpen] = useState(false)
const toggle = () => {
setIsOpen(!isOpen)
}
return (
<>
<Sidebar isOpen={isOpen} toggle={toggle} />
<NavBar toggle={toggle} />
<HeroSection />
<InfoSection {...homeObjOne} />
<InfoSection {...homeObjTwo} />
<InfoSection {...homeObjThree} />
</>
)
}
export default Home
Data.js
import reactImage from '../assets/images/react.svg'
import reactImg2 from '../assets/images/draw_and_publish.png'
export const homeObjOne = {
id: 'about',
lightBg: false,
lightText: false,
lightTextDescription: true,
topLine: 'Create SVGs for your configurators easily',
headLine: 'With the Map Tool',
description: 'Quickly and easily draw available lot using the drawing tool',
buttonLabel: 'Try it for free >',
imgStart: false,
img: require('../assets/images/react.svg'),
alt: 'Drawing',
dark: true,
primary: true,
darkText: false,
}
export const homeObjTwo = {
id: 'usage',
lightBg: true,
lightText: false,
lightTextDescription: false,
topLine: 'Usage is very easy, use these four steps to ',
headLine: 'create your first project',
description: 'Get to know the app easily in these 4 simple steps',
buttonLabel: 'Try it for free >',
imgStart: true,
img: {reactImage},
alt: 'Steps',
dark: true,
primary: true,
darkText: false,
}
export const homeObjThree = {
id: 'publish',
lightBg: false,
lightText: false,
lightTextDescription: true,
topLine: 'Publish your drawings',
headLine: 'as SVG or as an Iframe',
description: 'and import it into your projects',
buttonLabel: 'Try it for free >',
imgStart: false,
img: {reactImg2},
alt: 'Publish',
dark: true,
primary: true,
darkText: false,
}
Index.js
import React from 'react'
import { InfoContainer, InfoWrapper, InfoRow, Column1, Column2, TextWrapper, TopLine, Heading, Subtitle, BtnWrap, ImgWrap, Img } from './InfoElements'
import { Button } from '../ButtonElement'
// import reactImage from '../assets/images/react.svg'
//n
const InfoSection = ({ lightBg, id, imgStart, topLine, lightText, lightTextDescription, darkText, headLine, description, buttonLabel, img, alt, primary, dark, dark2 }) => {
return (
<>
<InfoContainer lightBg={lightBg} id={id}>
<InfoWrapper>
<InfoRow imgStart={imgStart}>
<Column1>
<TextWrapper>
<TopLine>
{/* Create SVGs for your configurators easily */}
{topLine}
</TopLine>
<Heading lightText={lightText}>
{/* With the Map Tool */}
{headLine}
</Heading>
<Subtitle darkText={darkText} lightTextDescription={lightTextDescription}>
{/* Quickly and easily draw available lot using the drawing tool */}
{description}
</Subtitle>
<BtnWrap>
<Button to='signup'
smooth={true}
duration={500}
spy={true}
exact="true"
offset={-80}
primary={primary ? 1 : 0}
dark={dark ? 1 : 0}
dark2={dark2 ? 1 : 0}>
{/* Try it for free */}
{buttonLabel}
</Button>
</BtnWrap>
</TextWrapper>
</Column1>
<Column2>
<ImgWrap>
<Img src={img} alt={alt} />
</ImgWrap>
</Column2>
</InfoRow>
</InfoWrapper>
</InfoContainer>
</>
)
}
export default InfoSection
InfoElements (styled components)
import styled from 'styled-components'
// import { homeObjOne } from './Data'
//$ variables dependant on Data.js property values
export const InfoContainer = styled.div`
color: #fff;
background: ${({ lightBg }) => (lightBg ? '#f9f9f9' : '#d3d3d3')};
font-family: 'Lato', sans-serif;
#media screen and (max-width: 768px) {
padding: 100px 0;
}
`
export const InfoWrapper = styled.div`
display: grid;
z-index: 1;
height: 860px;
width: 100%;
max-width: 1100px;
margin-right: auto;
margin-left: auto;
padding: 0 24px;
justify-content: center;
`
export const InfoRow = styled.div`
display: grid;
grid-auto-columns: minmax(auto, 1fr);
align-items: center;
grid-template-areas: ${({ imgStart }) => (imgStart ? `'col2 col1'` : `'col1 col2'`)};
#media screen and (max-width: 768px) {
grid-template-areas: ${({ imgStart }) => (imgStart ? `'col1' 'col2'` : `'col1 col1' 'col2 col2'`)};
}
`
export const Column1 = styled.div`
margin-bottom: 15px;
padding: 0 15px;
grid-area: col1;
`
export const Column2 = styled.div`
margin-bottom: 15px;
padding: 0 15px;
grid-area: col2;
`
export const TextWrapper = styled.div`
max-width: 540px;
padding-top: 0;
padding-bottom: 60px;
`
export const TopLine = styled.p`
color: #393939;
font-size: 16px;
line-height: 16px;
font-weight: 700;
letter-spacing: 1.4px;
text-transform: uppercase;
margin-bottom: 16px;
`
export const Heading = styled.h1`
margin-bottom: 24px;
font-size: 48px;
line-height: 1.1;
font-weight: 600;
color: ${({ lightText }) => (lightText ? '#f7f8fa' : '#000000')};
#media screen and (max-width: 480px) {
font-size: 32px;
}
`
export const Subtitle = styled.div`
max-width: 440px;
margin-bottom: 35px;
font-size: 18px;
font-family: 'Lato', sans-serif;
line-height: 24px;
color: ${({ darkText }) => (darkText ? '#ffffff' : '#000000')};
`
export const BtnWrap = styled.div`
display: flex;
justify-content: flex-start;
`
export const ImgWrap = styled.div`
max-width: 555px;
height: 100%;
`
export const Img = styled.div`
width: 100%;
margin: 0 0 10px 0;
padding: 0;
/* background-image: url('../images/react.svg'); */
`
**you img is a img component not a div component so rename its with img not a styled.div **
Does anyone know how to map a function to react-spring-3d-carousel? This is the code I have but nothing seems to be working. My data structure for portfolioItems is fine and ImageMedia renders one card only instead of mapping each card to its respective place.
It seems I am only able to render one car successfully, but not sure what's wrong here. Maybe if someone can look at the mapping function they'll be able to see where I'm screwing up.
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import Carousel from "react-spring-3d-carousel";
import { config } from "react-spring";
function Rotate(props) {
const table = props.cards.map((element, index) => {
return { ...element, onClick: () => setGoToSlide(index) };
});
const [offsetRadius, setOffsetRadius] = useState(0);
const [showArrows, setShowArrows] = useState(false);
const [goToSlide, setGoToSlide] = useState(null);
const [cards] = useState(table);
useEffect(() => {
setOffsetRadius(props.offset);
setShowArrows(props.showArrows);
}, [props.offset, props.showArrows]);
return (
<CarouselWrapper>
<Carousel
slides={cards}
currentSlide={goToSlide}
offsetRadius={offsetRadius}
showNavigation={showArrows}
animationConfig={config.gentle}
/>
</CarouselWrapper>
);
}
export default Rotate;
const CarouselWrapper = styled.div`
width: (${(props) => props.width});
height: (${(props) => props.height});
margin: (${(props) => props.margin});
`;
import React from "react";
import styled from "styled-components";
import Rotate from "./props/Rotate";
import portfolioItems from "./data/portfolioItems";
import ImageMediaCard from "./ImageMediaCard";
import { v4 as uuidv4 } from "uuid";
function Portfolio() {
let cards = [
{
key: uuidv4(),
content: <ImageMediaCard alt="1" />,
},
{
key: uuidv4(),
content: <ImageMediaCard alt="2" />,
},
{
key: uuidv4(),
content: <ImageMediaCard alt="3" />,
},
{
key: uuidv4(),
content: <ImageMediaCard alt="4" />,
},
];
return (
<PortfolioWrapper id="portfolio">
<PortfolioHeading>Portfolio</PortfolioHeading>
<Rotate
cards={
{key: uuidv(),
content: {portfolioItems.map((portfolioItem) => (
<ImageMediaCard
key={portfolioItem.id}
image={portfolioItem.image}
alt={portfolioItem.alt}
title={portfolioItem.title}
description={portfolioItem.description}
website={portfolioItem.website}
source={portfolioItem.source}
/>
))}}}
height="500px"
width="80%"
margin="10px"
offset={2}
showArrows={false}
/>
</PortfolioWrapper>
);
}
export default Portfolio;
const PortfolioWrapper = styled.div`
max-width: 100vw;
padding-bottom: 120px;
background-color: lightgreen;
height: 900px;
#media (max-width: 768px) {
display: block;
display: inline-block;
}
`;
const PortfolioHeading = styled.h2`
font-size: 60px;
display: flex;
justify-content: center;
`;
const RowWrap = styled.div`
padding-top: 20px;
`;
const Row = styled.div`
display: flex;
justify-content: space-around;
padding-bottom: 30px;
margin-left: 30px;
margin-right: 30px;
#media (max-width: 768px) {
flex-direction: column;
}
`;
const Cards = styled(Card)`
/* display: flex; */
/* justify-content: space-around; */
`;
Any help would be greatly appreciated!
I created a portal to display a message. come compile time i get an error on the ModalWrapper and cannot figure it out. all technologies are installed and up to date. ive imported and exported differently just can seem to figure out.
import React, { Component, Fragment } from 'react';
import styled from 'styled-components';
import Portal from './Portal';
import absolute from './elevation';
import Card from './Card';
import { Transition, animated } from 'react-spring/renderprops';
export default class Modal extends Component {
render() {
const { children, toggle, on } = this.props;
return (
<Portal>
<Transition
native
config={{
tension: 100,
friction: 15
}}
items={on}
from={{ opacity: 0, bgOpacity: 0, y: -50 }}
enter={{ opacity: 1, bgOpacity: 0.6, y: 0 }}
leave={{ opacity: 0, bgOpacity: 0, y: 50 }}
>
{on => on && ((styles) => (
<ModalWrapper>
<ModalCard styles={{
transform:
styles.y.interpolate(y => `translate3d(0, ${y}, 0)`),
...styles }}>
<CloseButton onClick={toggle}>
<p>H</p>
</CloseButton>
<div>{children}</div>
</ModalCard>
<Background style={{ opacity: styles.bgOpacity.interpolate(bgOpacity => bgOpacity) }} onClick={toggle}
/>
</ModalWrapper>
))}
</Transition>
</Portal>
);
}
}
const ModalWrapper = styled.div`
${absolute({})};
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`;
const ModalWrapper = styled.div`
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`;
I'm creating a DropDown List box and each item in the list has a remove (X) button to remove the item from the list. How is it possible to show the remove button "only" when the item is hovered over?
The current code shows the clear button each each item but I only want it to show when the item is hovered over
Sorry, here is the code
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const ListWrapper = styled.div`
position: absolute;
width: 16rem;
z-index: 1;
background: white;
&:hover {
cursor: pointer;
}
`;
const ListMenu = styled.div`
position: absolute;
width: 100%;
z-index: 1;
background: white;
overflow-x: hidden;
`;
const ListMenuHeader = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-end;
`;
const DropdownText = Text.Link.extend`
padding-top: 3rem;
`;
const DropdownButton = styled.div`
padding: 1 rem 0.75rem;
`;
const ListMenuItem = styled.div`
display: flex;
background-color: grey)};
color: grey};
>[name~=icon] {
right: 0rem;
border-radius: 0;
background: none;
align-items: right;
justify-content: right;
&:hover {
background-color: grey)};
}
&:focus {
outline: none;
}
`;
class ListListMenu extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
text: PropTypes.node.isRequired,
items: PropTypes.arrayOf(PropTypes.any).isRequired,
component: PropTypes.func.isRequired,
selectedItem: PropTypes.any,
getItemProps: PropTypes.func.isRequired,
highlightedIndex: PropTypes.number,
closeListMenu: PropTypes.func.isRequired,
};
static defaultProps = {
selectedItem: null,
highlightedIndex: null,
}
onClearClick = (items,item1) => (item) => {
const index = items.indexOf(item1);
if (index > -1) {
items.splice(index, 1);
}
}
render() {
const {
id, text, items, component, selectedItem, getItemProps,
highlightedIndex, closeListMenu,
} = this.props;
return (
<ListWrapper id={id} >
<ListMenuHeader onClick={closeListMenu}>
<DropdownText>{text}</DropdownText>
<DropdownButton
id={`${id}-button`}
>
<Icon type="caret-up" appearance="neutral" />
</DropdownButton>
</ListMenuHeader>
<ListMenu>
{selectedItems.map((item, index) => (
<ListMenuItem
{...getItemProps({
item,
isActive: highlightedIndex === index,
isSelected: _.isEqual(selectedItem, item),
})}
key={index}
>
{React.createElement(component, { item })}
<Button // CLEAR BUTTON
name={item}
id={item}
icon="remove"
onClick={this.onClearClick(items, item)}
circle
display="flat"
appearance="disabled"
id="clear-search-button"
/>
</ListMenuItem>
))}
</ListMenu>
</ListWrapper>
);
}
}
export default ListListMenu;
Here is one way you could probably just have that "x" appear on hover.
Instead of looking for a "hover" event, what about looking for an "onmouseenter" event combined with "onmouseleave"?
Like so...
class Example extends React.Component {
onHover() {
this.refs.deleteX.style.display = "block";
}
onExit() {
this.refs.deleteX.style.display = "none";
}
render() {
return (
<div>
<input onmouseenter={ this.onHover } onmouseleave={ this.onExit } />
<p ref="deleteX">x</p>
</div>
)
}
}
Kind of like this post