Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Css book layout (horizontal accordion)

I'm trying to create a page with a book layout, so a page with some tabs that use can expand one at a time.

Here a working example: 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 can notice some problems:

  1. when you expand a section, the title doesn't make a smooth transition with it pass to be vertical to horizontal. It should be a smooth rotation
  2. sometimes, I didn't understand exactly when, when you click a title, all the card seems to get closer to each other.
  3. another request is make the grey area all clickable but it is obvious a problem when it is open

Why? What's the problem? It there a better approach to do a layout like this?

like image 464
marielle Avatar asked Sep 17 '25 13:09

marielle


1 Answers

I fixed all of your problems as you said.

Problem #1 -> solved: you should use white-space:nowrap. if your text is large so in end of your box it will break to the next line. white-space:nowrap don't let that. (I don't recommend that cause this is not good for too long title text.)

Problem #2 -> solved: happens when you click some item and in middle of action (opening box) click on another one. this is because of display: flex and flexGrow: 1.

you used flex box and justifyContent: "center". so when you click and click on another one your wrapper getting smaller and all the cards seem to get closer to each other. flexGrow: 1 will break them. so flexGrow: 5 is solution.

Problem #3 -> solved: wrong place to set onClick. you should set onClick event on your box not on your text. and a condition for cursor is what you want. (item is selected so cursor must be default otherwise must be poiner).

Bonus :) if you set a small width to your wrapper so your rotate box will be prettier. it rotates at start of text and bingo.

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;
like image 87
Muhammad Habibpour Avatar answered Sep 20 '25 01:09

Muhammad Habibpour