Using the overflow-y property with a value of auto will hide any content that

I'm attempting to create a scrollable container for cards where the scrolling is restricted to the card content only, excluding the header and footer. My assumption is that the container should hide horizontal overflow and each card should have overflow-y: auto. However, implementing this setup causes the subsequent cards to become hidden.

function MainComponent() {
  const [activeCardIndex, setActiveCardIndex] = React.useState(0);

  const cardsContainerRef = React.useRef(null);
  const cardsRefs = React.useRef([]);

  const updateLayoutOffset = React.useCallback(() => {
    if (!cardsContainerRef.current || !cardsRefs.current[activeCardIndex]) {
      return 0;
    }

    const storyWidth = cardsRefs.current[activeCardIndex].offsetWidth;
    const containerWidth = cardsContainerRef.current.offsetWidth;
    let offset = containerWidth / 2 - storyWidth / 2 - activeCardIndex * storyWidth - activeCardIndex * 8;
    cardsContainerRef.current.style.transform = `translateX(${offset}px)`;
  }, [activeCardIndex]);

  React.useLayoutEffect(() => {
    updateLayoutOffset();
  }, [updateLayoutOffset]);

  React.useEffect(() => {
    updateLayoutOffset();
  }, [activeCardIndex, updateLayoutOffset]);

  const goToNextCard = React.useCallback(() => {
    if (activeCardIndex === cardsRefs.current.length - 1) {
      return;
    }

    setActiveCardIndex(activeCardIndex + 1);
  }, [activeCardIndex]);

  ...

  
              <p key={i} className="text-center">
                        {card}
                      </p>
                    ))}
                </div>
              ))}
            </div>

...

</script>

I haven't been able to identify a suitable combination of overflow settings yet.

Answer №1

I discovered a straightforward fix: by adding min-height: 0 to the cards container and using overflow: hidden

function MainComponent() {
  const [activeCardIndex, setActiveCardIndex] = React.useState(0);

  const cardsContainerRef = React.useRef(null);
  const cardsRefs = React.useRef([]);

  const updateLayoutOffset = React.useCallback(() => {
    if (!cardsContainerRef.current || !cardsRefs.current[activeCardIndex]) {
      return 0;
    }

    const storyWidth = cardsRefs.current[activeCardIndex].offsetWidth;
    const containerWidth = cardsContainerRef.current.offsetWidth;
    let offset = containerWidth / 2 - storyWidth / 2 - activeCardIndex * storyWidth - activeCardIndex * 8;
    cardsContainerRef.current.style.transform = `translateX(${offset}px)`;
  }, [activeCardIndex]);

  React.useLayoutEffect(() => {
    updateLayoutOffset();
  }, [updateLayoutOffset]);

  React.useEffect(() => {
    updateLayoutOffset();
  }, [activeCardIndex, updateLayoutOffset]);

  const goToNextCard = React.useCallback(() => {
    if (activeCardIndex === cardsRefs.current.length - 1) {
      return;
    }

    setActiveCardIndex(activeCardIndex + 1);
  }, [activeCardIndex]);

  const goToPreviousCard = React.useCallback(() => {
    if (activeCardIndex === 0) {
      return;
    }

    setActiveCardIndex(activeCardIndex - 1);
  }, [activeCardIndex]);

  let cards = ["Card 1", "Card 2", "Card 3"];

  return (
    <div className="flex h-dvh max-h-dvh w-full max-w-full flex-col justify-center overflow-hidden bg-black px-2 py-1 sm:bg-[#1a1a1a] sm:py-3">
      <div className="flex h-full max-h-full flex-row transition-transform duration-500">
        <div className="flex h-full max-w-full flex-row items-center sm:gap-4">
          <button className="rounded-md bg-blue-500 p-2 text-white" onClick={goToPreviousCard}>
            Previous
          </button>
          <div className="overflow-x-hidden flex aspect-[9/16] h-full max-h-full max-w-full flex-col rounded-md bg-black">
            <div className="flex flex-col gap-2 p-2">
              <h1 className="h-fit flex-1 text-left leading-none text-white">Header </h1>
            </div>
            <div ref={cardsContainerRef} className="min-h-0 flex grow flex-row items-center gap-2 transition-transform duration-500">
              {cards.map((card, index) => (
                <div
                  ref={(el) => {
                    if (el) {
                      cardsRefs.current[index] = el;
                    }
                  }}
                  key={index}
                  className="relative flex h-full min-w-full max-w-full flex-col gap-2 overflow-y-auto text-pretty rounded-md bg-white p-2 dark:bg-[#343434]"
                >
                  {Array((index + 1) * 4)
                    .fill(0)
                    .map((_, i) => (
                      <p key={i} className="text-center">
                        {card}
                      </p>
                    ))}
                </div>
              ))}
            </div>
            <div className="flex w-full flex-row items-center p-2">
              <input className="min-w-0 flex-1 rounded-full border-[1px] bg-transparent px-4 py-2 text-left" />
            </div>
          </div>
          <button className="rounded-md bg-blue-500 p-2 text-white" onClick={goToNextCard}>
            Next
          </button>
        </div>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<MainComponent />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

Answer №2

I encountered a similar problem in my project and decided to modify my code based on your approach.

Here is the MainComponent:

'use client'

import React, { useRef, useState, useCallback, useLayoutEffect, useEffect } from 'react'
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
import { ChevronLeft, ChevronRight } from "lucide-react"

export default function MainComponent() {
  const [activeCardIndex, setActiveCardIndex] = useState(0)
  const cardsContainerRef = useRef<HTMLDivElement>(null)
  const cardsRefs = useRef<(HTMLDivElement | null)[]>([])

  const updateLayoutOffset = useCallback(() => {
    if (!cardsContainerRef.current || !cardsRefs.current[activeCardIndex]) {
      return
    }

    const cardWidth = cardsRefs.current[activeCardIndex]!.offsetWidth
    const containerWidth = cardsContainerRef.current.offsetWidth
    let offset = containerWidth / 2 - cardWidth / 2 - activeCardIndex * cardWidth
    cardsContainerRef.current.style.transform = `translateX(${offset}px)`
  }, [activeCardIndex])

  useLayoutEffect(() => {
    updateLayoutOffset()
  }, [updateLayoutOffset])

  useEffect(() => {
    updateLayoutOffset()
    window.addEventListener('resize', updateLayoutOffset)
    return () => window.removeEventListener('resize', updateLayoutOffset)
  }, [activeCardIndex, updateLayoutOffset])

  const goToNextCard = useCallback(() => {
    setActiveCardIndex((prev) => Math.min(prev + 1, cardsRefs.current.length - 1))
  }, [])

  const goToPreviousCard = useCallback(() => {
    setActiveCardIndex((prev) => Math.max(prev - 1, 0))
  }, [])

  const cards = ["Card 1", "Card 2", "Card 3"]

  return (
    <div className="flex h-dvh max-h-dvh w-full max-w-full flex-col justify-center overflow-hidden bg-background px-2 py-1 sm:py-3">
      <div className="flex h-full max-h-full flex-row items-center gap-4">
        <Button
          variant="outline"
          size="icon"
          onClick={goToPreviousCard}
          disabled={activeCardIndex === 0}
        >
          <ChevronLeft className="h-4 w-4" />
        </Button>
        <div className="relative flex aspect-[9/16] h-full max-h-full max-w-full flex-col overflow-hidden rounded-md bg-card">
          <div
            ref={cardsContainerRef}
            className="flex h-full transition-transform duration-500 ease-in-out"
          >
            {cards.map((card, index) => (
              <Card
                key={index}
                ref={(el) => (cardsRefs.current[index] = el)}
                className="h-full w-full shrink-0 overflow-hidden"
              >
                <CardHeader className="h-16">
                  <h2 className="text-xl font-semibold">{card}</h2>
                </CardHeader>
                <CardContent className="h-[calc(100%-8rem)] overflow-y-auto">
                  <div className="space-y-2">
                    {Array(40).fill(0).map((_, i) => (
                      <p key={i} className="text-center">
                        {card} Content {i + 1}
                      </p>
                    ))}
                  </div>
                </CardContent>
                <CardFooter className="h-16">
                  <Input className="w-full" placeholder="Type a message..." />
                </CardFooter>
              </Card>
            ))}
          </div>
        </div>
        <Button
          variant="outline"
          size="icon"
          onClick={goToNextCard}
          disabled={activeCardIndex === cards.length - 1}
        >
          <ChevronRight className="h-4 w-4" />
        </Button>
      </div>
    </div>
  )
}

Button.tsx Component:

import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive:
          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline:
          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : "button";
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    )
  }
)
Button.displayName = "Button";

export { Button, buttonVariants }

Input.tsx component:

import * as React from "react"

import { cn } from "@/lib/utils"

export interface InputProps
  extends React.InputHTMLAttributes<HTMLInputElement> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ className, type, ...props }, ref) => {
    return (
      <input
        type={type}
        className={cn(
          "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
          className
        )}
        ref={ref}
        {...props}
      />
    )
  }
)
Input.displayName = "Input";

export { Input }

Card.tsx component:

import * as React from "react"

import { cn } from "@/lib/utils"

const Card = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn(
      "rounded-lg border bg-card text-card-foreground shadow-sm",
      className
    )}
    {...props}
  />
))
Card.displayName = "Card";

const CardHeader = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("flex flex-col space-y-1.5 p-6", className)}
    {...props}
  />
))
CardHeader.displayName = "CardHeader";

const CardTitle = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
  <h3
    ref={ref}
    className={cn(
      "text-2xl font-semibold leading-none tracking-tight",
      className
    )}
    {...props}
  />
))
CardTitle.displayName = "CardTitle";

const CardDescription = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
  <p
    ref={ref}
    className={cn("text-sm text-muted-foreground", className)}
    {...props}
  />
))
CardDescription.displayName = "CardDescription";

const CardContent = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent";

const CardFooter = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("flex items-center p-6 pt-0", className)}
    {...props}
  />
))
CardFooter.displayName = "CardFooter";

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Reacotron's presenting behaviors prior to sagas

I recently installed Reactotron and encountered an unusual error. Let me walk you through my process flow to help you understand the issue: 1.) The app is starting 2.) I dispatch an ON_START_APP action 3.) I utilize takeEvery to handle the ON_START_APP a ...

Creating a tiny arrow using CSS

Can anyone advise on the best way to create a small arrow design using CSS? I have tried using a regular closing > arrow, but it's not quite what I'm looking for. https://i.stack.imgur.com/7uLMG.png ...

Steps for verifying the status of a Bootstrap checkbox in a ReactJS application

I'm currently working on a React.js component where I need to check if a checkbox is checked and then save that information in a Firebase Realtime database. My issue right now is that the checkbox only registers as checked after 3 clicks. When I cons ...

Using React Hook Form with the TipTap Editor: A Comprehensive Guide

I have a text input set up using the React Hook Form with the following code: <div className='mb-4'> <label htmlFor='desc'>Description</label> < ...

Fixing Disappearing CSS Arrows with :after Pseudo-element

On a vertical sub-nav found at , I have a css arrow that is not displaying correctly. It should be on the outer right hand side, but part of it is being cut off. If I try to move the arrow further to the right, it disappears altogether. What can I do to en ...

The appended element continues to add infinitely as you scroll, when it should only be appended once

I have successfully added a div to a form after scrolling, but now I am facing an issue where this div keeps appearing indefinitely as the user scrolls. You can view this troublesome div on the following website: . It is the close button that appears at t ...

How can two distinct React projects be effectively combined?

In the midst of a major react project, I find myself in need of a chat application. Luckily, I've already created the ideal solution using react-chat-engine. Now comes the challenge of integrating this chat app with my main application. While I could ...

Despite element being a grandchild, the function this.wrapperRef.current.contains(element) will return false

The issue arises when using the EditModal component with an onClickOutside event. This component includes a child element, a Material-UI Select, where clicking on a MenuItem triggers the onClickOutside event, causing the modal to close without selecting th ...

Switching images between mobile and desktop view in responsive mode is a useful feature for optimizing

Within a specific folder structure, I have a collection of images designated for both desktop and mobile displays: img: img-1.png img-2.png img-3.png img-4.png img-5.png img-6.png img/mobile: img-1.png img-2.png ...

What is the best way to trigger a JavaScript onclick event for a button that is located within a dropdown menu on an HTML page

I've implemented a feature that resizes the font size within a text area based on the selected font size. It was functioning flawlessly... until I decided to place the font size selection in a drop-down menu, which caused the feature to stop working. ...

Guide to validating password and confirm password fields with React Hook Form and Yup

const formOption = { config = [ { label: 'Password', name: 'password', type: 'password', rules: yup.string().required() }, { label: 'Confirm password', ...

Tips on customizing the appearance of two Material-UI Sliders across separate components

Looking to customize two sliders, each within their own react component. Slider in the first component const customizedThemeSlider1 = createTheme({ overrides:{ MuiSlider: { thumb:{ color: "#4442a9", marg ...

Create a CSS menu that remains fixed at the top of the page and highlights the current submenu as

When explaining a concept, I find it easier to include a drawing to ensure clarity. https://i.stack.imgur.com/IChFy.png My goal is to keep the menu at the top of the page after scrolling past the header. https://i.stack.imgur.com/3fzJ6.png Using Bootst ...

NextJs14 application with a Shadcn ComboBox form and a Virtualized TanSack list

I have developed a client component in Next.js that includes a form with a dropdown and search functionality. The component utilizes the popular Shadcn library along with Tanstack virtualizer to handle a large list of items efficiently. Despite my effort ...

Issue in React: Syntax error caused by using import statement outside a module

Upon integrating the mui-tel-input package into my React project, I encountered an error during the build process that has left me puzzled about how to resolve it. Linting and checking validity of types ▲ Next.js 14.0.4 ✓ Creating an optimized pro ...

all elements with equal height within a flexbox positioned at the bottom

Check out this HTML code snippet I have for constructing a grid using Bootstrap 4: <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOh ...

Is there a way to replace the message 'no rows to show' with a custom component in ag-Grid for react?

Currently, I am utilizing ag-grid in my React application https://www.ag-grid.com. When the table or grid is empty, it displays the default message "No rows to show." However, I am interested in customizing this message or possibly incorporating a custom ...

Automatically expand all PrimeNG Accordion panels for easy printing purposes

I've implemented the PrimeNG library's accordion component in my angular project. You can find more information here. In my template, I have some custom css styling for printing the page that looks like this: @media print { .profile-progress ...

my divs are not being affected by the max-width property and are retaining their

I've been learning CSS and HTML through an interesting tutorial on Udacity. Here's the code I've been working on: .image{ max-width: 50%; } .app{ display: flex; } .description{ color: red; max-width: 705px; } <h1 class="title" ...

The React component is experiencing a delay in its updates

I've been experiencing delayed updates when using React.useEffect(). Can anyone shed some light on why this might be happening? function Process(props) { const [results, setResults] = React.useState({ number: "", f: {} }); let ...