Horizontal Accordion Design for Cascading Style Sheets (CSS) Books

I am currently working on developing a page that features a book layout. This page will include tabs that users can expand individually.

If you would like to see a working example, you can check out this link: https://codesandbox.io/s/book-layout-l28gh?file=/src/App.js:0-1419

import { useState } from "react";

const dataset = [
  { name: "A section", description: "page A" },
  { name: "B section", description: "page B" },
  { name: "C section with long title", description: "page C" },
  { name: "D section", description: "page D" }
];

export default function App() {
  return <Page />;
}

function Page({}) {
  const [openSection, setOpenSection] = useState(0);

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100vh"
      }}
    >
      {dataset.map((datum, i) => {
        const { name } = datum;
        const isOpen = i === openSection;

        return (
          <div
            key={name}
            style={{
              height: "100%",
              backgroundColor: isOpen ? "white" : "lightgray",
              border: `1px solid ${isOpen ? "white" : "black"}`,
              padding: 10,
              flex: 1,
              flexGrow: isOpen ? 1 : 0,
              transition: "all 2s ease"
            }}
          >
            <div
              style={{
                cursor: "pointer",
                writingMode: isOpen ? "horizontal-tb" : "vertical-rl",
                transition: "all 2s ease"
              }}
              onClick={() => setOpenSection(i)}
            >
              {name}
            </div>
          </div>
        );
      })}
    </div>
  );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

If you test it, you may encounter a few issues:

  1. When you expand a section, the title does not smoothly transition from vertical to horizontal. It should rotate smoothly.
  2. There is an inconsistency where, at times, when you click on a title, all the cards seem to move closer together.
  3. There is a request to make the grey area clickable, but there is a clear issue when it is opened.

What could be the cause of these problems? Is there a better method to implement a layout like this?

Answer №1

I have successfully resolved all the issues you pointed out.

Problem #1 -> fixed: To prevent text from breaking to the next line at the end of the box, I have applied the white-space: nowrap CSS property. However, please note that using this property may not be ideal for long title text.

Problem #2 -> fixed: I have addressed the issue that occurs when clicking on one item while the action of opening a box is in progress. This was caused by the combination of display: flex and flexGrow: 1. I have set flexGrow: 5 as a solution to prevent this behavior.

Problem #3 -> fixed: I have corrected the placement of the onClick event to be set on the box rather than the text. Additionally, I have added a condition to change the cursor style based on whether the item is selected or not.

Bonus :) I have enhanced the appearance of the rotating box by setting a small width to the wrapper, resulting in a prettier rotation effect.

import { useState } from "react";

const dataset = [
  { name: "A section" },
  { name: "B section" },
  { name: "C section with long title" },
  { name: "D section" },
  { name: "E section" },
];

function Page() {
  const [openSection, setOpenSection] = useState(1);

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100vh",
      }}
    >
      {dataset.map((datum, i) => {
        const { name } = datum;
        const isOpen = i === openSection;
        return (
          <div
            key={name}
            onClick={() => setOpenSection(i)}
            style={{
              height: "100%",
              backgroundColor: isOpen ? "white" : "lightgray",
              border: `1px solid ${isOpen ? "white" : "black"}`,
              padding: "20px 30px",
              flex: 1,
              flexGrow: isOpen ? 5 : 0,
              transition: "all 1s linear",
              boxSizing: "border-box",
              cursor: openSection !== i ? 'pointer' : 'default',

              "&:first-child": {
                left: 0,
              },

              "&:last-child": {
                right: 0,
              },
            }}
          >
            <div
              style={{
                transform: `rotate(${isOpen ? "0" : "90"}deg)`,
                transition: "all 1s linear",
                width: 1,
                whiteSpace: "nowrap",
              }}
            >
              {name}
            </div>
          </div>
        );
      })}
    </div>
  );
}

export default Page;

Answer №2

  1. Transition improvement: Apply the CSS rule white-space:nowrap to maintain text size consistency during size adjustments. Position the title div absolutely to prevent layout disruption during transitions.
  2. Clicking issue: I was unable to replicate the bug. The problem might be due to Codesandbox not updating properly. Try refreshing the output page and testing again, or refer to the modified Codesandbox link below.
  3. Improved clickability: Enhance the gray area's clickability by moving the mouse pointer and click handler to the parent element with an isOpen condition.

Access the modified Codesandbox here:

import { useState } from "react";

const dataset = [
  { name: "A section" },
  { name: "B section" },
  { name: "C section with long title" },
  { name: "D section" },
  { name: "E section" }
];

export default function App() {
  return <Page />;
}

function Page({}) {
  const [openSection, setOpenSection] = useState(0);

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100vh"
      }}
    >
      {dataset.map((datum, i) => {
        const { name } = datum;
        const isOpen = i === openSection;

        return (
          <div
            key={name}
            style={{
              height: "100%",
              backgroundColor: isOpen ? "white" : "lightgray",
              border: `1px solid ${isOpen ? "white" : "black"}`,
              flex: 1,
              flexGrow: isOpen ? 1 : 0,
              transition: "all 2s ease",

              //my changes
              padding: 0,
              flexBasis: "1.2rem",
              cursor: !isOpen ? "pointer" : "auto",
              position: "relative"
            }}
            onClick={!isOpen ? () => setOpenSection(i) : null}
          >
            <div
              style={{
                transition: "all 2s ease",

                //my changes
                transform: `rotate(${isOpen ? "0" : "90"}deg) 
                translateX(${isOpen ? "0" : "50"}%)`,
                whiteSpace: "nowrap",
                width: isOpen ? "100%" : "1rem",
                position: "absolute",
                top: isOpen ? "1rem" : "0",
                left: isOpen ? "1rem" : "0",
                fontWeight: "bold"
              }}
            >
              {name}
            </div>
          </div>
        );
      })}
    </div>
  );
}

Answer №3

I plan to address your concerns in the same order you raised them.

  1. writing-mode that is currently being used on the titles cannot be animated. Consider trying text rotation instead with the transform property
  2. If you aim to make the entire grey area clickable, it's recommended to relocate both the onClick function and the cursor: "pointer" property to the parent div containing the specific element, in this case, the title.

Answer №4

Enhance your code with clickable grey area and smooth transitions

import { useState } from "react";

const dataset = [
  { name: "Section A" },
  { name: "Section B" },
  { name: "Section C with long title" },
  { name: "Section D" },
  { name: "Section E" }
];

export default function App() {
  return <Page />;
}

function Page({}) {
  const [openSection, setOpenSection] = useState(0);

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100vh"
      }}
    >
      {dataset.map((datum, i) => {
        const { name } = datum;
        const isOpen = i === openSection;

        return (
          <div
            key={name}
            style={{
              height: "100%",
              backgroundColor: isOpen ? "white" : "lightgray",
              border: `1px solid ${isOpen ? "white" : "black"}`,
              padding: 10,
              flex: 1,
              flexGrow: isOpen ? 1 : 0,
              cursor: "pointer",
              writingMode: isOpen ? "horizontal-tb" : "vertical-rl",
              transition: "all 2s ease"
            }}
          >
            <div
              style={{
               transition: "all 2s ease"
              }}
              onClick={() => setOpenSection(i)}
            >
              {name}
            </div>
          </div>
        );
      })}
    </div>
  );
}

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

WordPress AJAX code encountered a http400 Bad Request

I've recently started delving into website development and am currently working on a WordPress site. The issue I'm facing is similar to another query on SO, but that question doesn't involve jQuery.AJAX; instead, it utilizes jQuery.post with ...

An Alternative Approach for Executing a Function within an AngularJS Directive

I am currently working on a unique element directive that displays a user's picture and name. This directive has various configuration attributes, one of which is the uc-on-hover attribute. The purpose of this attribute is to determine what element sh ...

What is the best way to transfer the content from a tinyMCE textarea editor to an inner controller using Symfony3 and Ajax

I have two small rich text editors identified as #homepage and #thankyoupage. My goal is to submit the content of these TinyMCE text areas to a Symfony controller. Below is my front-end implementation: https://i.stack.imgur.com/TE1Ys.jpg Currently, I am ...

Tips for adding content to a textarea after making manual changes to its existing content

One issue I encountered is with capturing the enter key on an input field and appending it to a textarea. While this works initially, any edits made directly on the textarea seem to disrupt the functionality. How can this be resolved? I have tested this i ...

How come my MySQL date is decreasing by one day when using JavaScript?

My todo list is stored in a MySQL database with columns for todoTitle and todoDate. However, when I display the todoDate on my website, it shows the date decremented by one day. For example, if the date in the database is 2016-12-20, it will show as 2016-1 ...

javascript categorize data by key and display in a tabular format

I have a Client ID and gender information available. Shown below is a JSON response along with a JavaScript function to display the data in a table format. The JSON response structure is as follows: studies = [{ "id": { "Value&qu ...

When attempting to pass Rgraph image data through a jQuery AJAX call, a 403 Forbidden error is being

I have been working on a project that involves creating graphs/charts using the Rgraph PHP library. To generate these charts, my script follows these steps: Calculate the graph points and render the graph using the Rgraph Draw() method. Create an image d ...

Express server is receiving undefined post parameters from Axios in Vue, even though they are clearly defined in Vue

Within my code, I am utilizing an <img> element as shown below: <img v-bind:word = "thing" @click="action" align="center" src="../assets/pic.png"/> Alongside, there is a method structured in this manner: ...

Caution: It is important to provide a unique "key" prop for each child in a list in my React Native application

When creating a Custom Side Menu for the Drawer menu in React Native, I passed some props but encountered a warning message: Warning: Each child in a list should have a unique "key" prop. CustomSideMenu.js /* eslint-disable prettier/prettier */ import R ...

Why is the error message "Invalid field name: '$conditionalHandlers' in 'collaborators..$conditionalHandlers'" popping up now?

Currently, in my Node/Express/Mongoose application (latest versions), I am working on a feature that involves "Projects" with a list of "collaborators" identified by the IDS of "Users". To simplify complex aggregations, I have decided to store these IDS as ...

The Glyphicon icon fails to appear on the initial page load and only shows up after refreshing the

I have been utilizing bootstrap.min.css from bootstrap v3.3.5 which I downloaded from http://getbootstrap.com and used it locally. However, I encountered an issue with glyphicons when running it on IE 9 and above. The glyphicon icon disappears on the first ...

Can you explain the purpose of prevState within the setState method of a functional component?

When returning the updated previous state within a setState method retrieved from the useState hook, it appears that the state remains unchanged. To demonstrate this behavior, consider running the following code snippet: function App(){ const [state, ...

Creating a dynamic circle that expands in size based on the duration the user presses down (using Java Script)

I have a fun challenge for you! I want to create a growing circle on a canvas based on how long you hold your mouse button. Currently, I can draw a fixed width circle where my mouse was when I clicked, but I need it to dynamically change size as you hold t ...

SweetAlert will only render if it is not hidden

One of the components in my codebase utilizes SweetAlert from "react-bootstrap-sweetalert". The issue I am facing is that even when the "show" property is set to false, SweetAlert is still being rendered and the function inside it gets called regardless o ...

What steps should I take to create a stylish HTML profile with well-organized CSS?

Looking for a way to display photos, biographies, personal details, and more on a webpage? We've got you covered! You can customize the layout using semantic HTML and CSS styles. Build your design online at http://jsfiddle.net/ and thank us later! &l ...

Exploring z-indices in event bubbling

JSFiddle: https://jsfiddle.net/uLap7yeq/19/ Issue Let's examine a scenario where there are two elements, canvas and div, positioned in the same location using CSS. The div has a higher z-index compared to the canvas, but how can we make sure events ...

Stop Sublime Text from automatically calculating Less2Css

Within my workflow using Sublime Text and Less2Css for dealing with less files, I encountered an issue where specifying the max-height of a container as 100% - 20px resulted in minification reducing it to '80%'. Is there a way to work around this ...

The callback function used for the database query did not provide any results

So here's a code snippet: module.exports.checkEmailInUse = (email) => { connection.query('SELECT `id` FROM `users` WHERE email = ?', [ email ], function(err, rows, fields) { console. ...

How do I determine whether an object is a Map Iterator using JavaScript?

I'm working on some NodeJS code that involves a Map Iterator object. How can I accurately determine if a Javascript object is a "Map Iterator"? Here are the methods I have attempted: typeof myMap.keys() returns 'Object' typeof myMap.keys() ...

Surprising Outcomes of Negative Margin in jQuery Animation

Unique Project Summary I am currently working on a website that incorporates a sliding menu feature. I have successfully implemented this functionality, and the display remains consistent during the animation transitions for sliding the menu in and out. T ...