1

I'm not very good at CSS, in fact, I barely write CSS due to the rise of libraries like MUI. I have found myself in a weird situation, I'm trying to rewrite this sample Emabla caurousel in typescript and NextJS.

The challenge I am facing is with the CSS, If I import it globally everything works fine but I do not want to do that, I want to import it on the component level. My code structure looks like this:

Code structure

The ProductSlider.tsx is the EmblaCarousel.js in the sample project, the ProductSlider.module.css is the embla.css

My ProductSlider looks like this:

import s from './ProductSlider.module.css'
import React, { useState, useEffect, useCallback, memo } from 'react'
import useEmblaCarousel from 'embla-carousel-react'
import Thumbnail from './Thumbnail'
import Image from 'next/image'
import { Box } from '@mui/material'

type ProductSliderProps = {
items: {
    url: string
    alt?: string
}[]
}

const ProductSlider = ({ items }: ProductSliderProps) => {
const [selectedIndex, setSelectedIndex] = useState(0)
const [mainViewportRef, embla] = useEmblaCarousel({ skipSnaps: false })
const [thumbViewportRef, emblaThumbs] = useEmblaCarousel({
    containScroll: 'keepSnaps',
    dragFree: true,
})

const onThumbClick = useCallback(
    (index) => {
    if (!embla || !emblaThumbs) return
    if (emblaThumbs.clickAllowed()) embla.scrollTo(index)
    },
    [embla, emblaThumbs]
)

const onSelect = useCallback(() => {
    if (!embla || !emblaThumbs) return
    setSelectedIndex(embla.selectedScrollSnap())
    emblaThumbs.scrollTo(embla.selectedScrollSnap())
}, [embla, emblaThumbs, setSelectedIndex])

useEffect(() => {
    if (!embla) return
    onSelect()
    embla.on('select', onSelect)
}, [embla, onSelect])

return (
    <>
    <Box className={s.embla}>
        <Box className={s.embla__viewport} ref={mainViewportRef}>
        <Box className={s.embla__container}>
            {items.map(({ url, alt }, idx) => (
            <Box className={s.embla__slide} key={idx}>
                <Box className={s.embla__slide__inner}>
                <Image
                    src={url}
                    alt={alt}
                    layout="fill"
                    objectFit="contain"
                    quality="85"
                />
                </Box>
            </Box>
            ))}
        </Box>
        </Box>
    </Box>

    <Box className="embla embla--thumb">
        <Box className={s.embla__viewport} ref={thumbViewportRef}>
        <Box className="embla__container embla__container--thumb">
            {items.map(({ url, alt }, idx) => (
            <Thumbnail
                onClick={() => onThumbClick(idx)}
                selected={idx === selectedIndex}
                imgSrc={url}
                key={idx}
                alt={alt}
                s={s}
            />
            ))}
        </Box>
        </Box>
    </Box>
    </>
)
}

export default memo(ProductSlider)

As you can see I have managed to get the straightforward CSS working, the challenge is, how do I call something like this embla__container embla__container--thumb using the named import?

or this

<div
className={`embla__slide embla__slide--thumb ${
selected ? 'is-selected' : ''
}`}

I understand in cases where I have two classes like this embla__container embla__container I can use clsx like this cn(s.embla__container, s.embla__container) but in a case where I have something like this --thumb, some_name--thumb, some_name-is-selected or this is-selected I'm not sure what to do.

2 Answers 2

0

Try to do this

import s from './ProductSlider.module.css'
import React, { useState, useEffect, useCallback, memo } from 'react'
import useEmblaCarousel from 'embla-carousel-react'
import Thumbnail from './Thumbnail'
import Image from 'next/image'
import { Box } from '@mui/material'

type ProductSliderProps = {
items: {
    url: string
    alt?: string
}[]
}

const ProductSlider = ({ items }: ProductSliderProps) => {
const [selectedIndex, setSelectedIndex] = useState(0)
const [mainViewportRef, embla] = useEmblaCarousel({ skipSnaps: false })
const [thumbViewportRef, emblaThumbs] = useEmblaCarousel({
    containScroll: 'keepSnaps',
    dragFree: true,
})

const onThumbClick = useCallback(
    (index) => {
    if (!embla || !emblaThumbs) return
    if (emblaThumbs.clickAllowed()) embla.scrollTo(index)
    },
    [embla, emblaThumbs]
)

const onSelect = useCallback(() => {
    if (!embla || !emblaThumbs) return
    setSelectedIndex(embla.selectedScrollSnap())
    emblaThumbs.scrollTo(embla.selectedScrollSnap())
}, [embla, emblaThumbs, setSelectedIndex])

useEffect(() => {
    if (!embla) return
    onSelect()
    embla.on('select', onSelect)
}, [embla, onSelect])

return (
    <>
    <Box className={s.embla}>
        <Box className={s.embla__viewport} ref={mainViewportRef}>
        <Box className={s.embla__container}>
            {items.map(({ url, alt }, idx) => (
            <Box className={s.embla__slide} key={idx}>
                <Box className={s.embla__slide__inner}>
                <Image
                    src={url}
                    alt={alt}
                    layout="fill"
                    objectFit="contain"
                    quality="85"
                />
                </Box>
            </Box>
            ))}
        </Box>
        </Box>
    </Box>

    <Box className="embla embla--thumb">
        <Box className={s.embla__viewport} ref={thumbViewportRef}>
        <Box className="embla__container embla__container--thumb">
            {items.map(({ url, alt }, idx) => (
            <Thumbnail
                onClick={() => onThumbClick(idx)}
                selected={idx === selectedIndex}
                imgSrc={url}
                key={idx}
                alt={alt}
                s={s}
            />
            ))}
        </Box>
        </Box>
    </Box>




    <style>
        {
        `
        // Your Css Here     
        
        
        `
        }
    </style>

    </>
)
}

export default memo(ProductSlider)
Sign up to request clarification or add additional context in comments.

1 Comment

This has not worked. The CSS is flagged by typescript.
0

Based on this answer I ended up doing this and it worked:

import s from './ProductSlider.module.css'
import React, { useState, useEffect, useCallback, memo } from 'react'
import useEmblaCarousel from 'embla-carousel-react'
import Thumbnail from './Thumbnail'
import Image from 'next/image'
import { Box } from '@mui/material'

type ProductSliderProps = {
items: {
    url: string
    alt?: string
}[]
}

const ProductSlider = ({ items }: ProductSliderProps) => {
const [selectedIndex, setSelectedIndex] = useState(0)
const [mainViewportRef, embla] = useEmblaCarousel({ skipSnaps: false })
const [thumbViewportRef, emblaThumbs] = useEmblaCarousel({
    containScroll: 'keepSnaps',
    dragFree: true,
})

const onThumbClick = useCallback(
    (index) => {
    if (!embla || !emblaThumbs) return
    if (emblaThumbs.clickAllowed()) embla.scrollTo(index)
    },
    [embla, emblaThumbs]
)

const onSelect = useCallback(() => {
    if (!embla || !emblaThumbs) return
    setSelectedIndex(embla.selectedScrollSnap())
    emblaThumbs.scrollTo(embla.selectedScrollSnap())
}, [embla, emblaThumbs, setSelectedIndex])

useEffect(() => {
    if (!embla) return
    onSelect()
    embla.on('select', onSelect)
}, [embla, onSelect])

return (
    <>
    <Box className={s.embla}>
        <Box className={s.embla__viewport} ref={mainViewportRef}>
        <Box className={s.embla__container}>
            {items.map(({ url, alt }, idx) => (
            <Box className={s.embla__slide} key={idx}>
                <Box className={s.embla__slide__inner}>
                <Image
                    src={url}
                    alt={alt}
                    layout="fill"
                    objectFit="contain"
                    quality="85"
                />
                </Box>
            </Box>
            ))}
        </Box>
        </Box>
    </Box>

    <Box className={`${s.embla} ${s['embla--thumb']}`}>
        <Box className={s.embla__viewport} ref={thumbViewportRef}>
        <Box
            className={`${s.embla__container} ${s['embla__container--thumb']}`}
        >
            {items.map(({ url, alt }, idx) => (
            <Thumbnail
                onClick={() => onThumbClick(idx)}
                selected={idx === selectedIndex}
                imgSrc={url}
                key={idx}
                alt={alt}
                s={s}
            />
            ))}
        </Box>
        </Box>
    </Box>
    </>
)
}

export default memo(ProductSlider)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.