O menu não está abrindo uma div de índice correta

11

Cara, estou enfrentando um problema. Eu tenho dados bidimensionais. Os dados têm uma estrutura aninhada que contém links.

const data = [
  // First Div Panel 
  [
    {
      id: 1,
      url: "/services",
      title: "Services"
    },
    {
      id: 2,
      title: "Products",
      children: [
        {
          id: 3,
          url: "/themes-templates",
          title: "Themes & Templates"
        },
        {
          id: 4,
          url: "/open-source",
          title: "Open Source"
        },
        {
          id: 5,
          url: "/solutions",
          title: "Solutions"
        }
      ]
    },
    {
      id: 6,
      url: "/work",
      title: "Work",
      children: [
        {
          id: 7,
          url: "/methodology",
          title: "Methodology",
          children: [
            {
              id: 8,
              url: "/agile",
              title: "Agile",
              children: [
                {
                  id: 9,
                  url: "/scrum",
                  title: "Scrum"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      id: 10,
      url: "/contact-us",
      title: "Contact Us"
    }
  ],
  // Second Div Panel which contains children of second list item
  [
    {
      id: 3,
      url: "/themes-templates",
      title: "Themes & Templates"
    },
    {
      id: 4,
      url: "/open-source",
      title: "Open Source"
    },
    {
      id: 5,
      url: "/solutions",
      title: "Solutions"
    }
  ],
  // Third Div Panel which contains children of third list item
  [
    {
      id: 7,
      url: "/methodology",
      title: "Methodology",
      children: [
        {
          id: 8,
          url: "/agile",
          title: "Agile",
          children: [
            {
              id: 9,
              url: "/scrum",
              title: "Scrum"
            }
          ]
        }
      ]
    }
  ],
  // Fourth Div Panel contains the children of the 3rd sub list item
  [
    {
      id: 8,
      url: "/agile",
      title: "Agile",
      children: [
        {
          id: 9,
          url: "/scrum",
          title: "Scrum"
        }
      ]
    }
  ],
  // Fourth Div Panel contains the children of the 3rd sub sub list item
  [
    {
      id: 9,
      url: "/scrum",
      title: "Scrum"
    }
  ]
];

Minha tarefa é utilizar esses dados bidimensionais e criar um menu móvel reactcom uma estrutura semelhante a um painel de pressão.

No entanto, eu tento criar assim. O que fiz foi tratar cada sub-matriz como um painel separado div. Primeiro, os itens da sub-matriz serão considerados no painel raiz, que por padrão é visível. Se um item tiver uma childrenpropriedade, significa que um nextbotão dinamicamente é gerado nesse item da lista. Quando clicamos neste botão, ele adicionará uma is-visibleclasse no painel. Mas, a questão é como rastrear qual painel está associado a esse clique no botão ? Eu tento usar um estado com activeIde prevIdMas minha indexação não está funcionando corretamente e não está abrindo um painel correto. Você pode inspecionar minha solução no painel do inspetor chrome. Agradeço se você me disser o que estou fazendo de errado.

O meu código sandbox Link

Código:

// Get a hook function
const {useState} = React;

//#region Data
const data = [
  // First Div Panel
  [
    {
      id: 1,
      url: "/services",
      title: "Services"
    },
    {
      id: 2,
      title: "Products",
      children: [
        {
          id: 3,
          url: "/themes-templates",
          title: "Themes & Templates"
        },
        {
          id: 4,
          url: "/open-source",
          title: "Open Source"
        },
        {
          id: 5,
          url: "/solutions",
          title: "Solutions"
        }
      ]
    },
    {
      id: 6,
      url: "/work",
      title: "Work",
      children: [
        {
          id: 7,
          url: "/methodology",
          title: "Methodology",
          children: [
            {
              id: 8,
              url: "/agile",
              title: "Agile",
              children: [
                {
                  id: 9,
                  url: "/scrum",
                  title: "Scrum"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      id: 10,
      url: "/contact-us",
      title: "Contact Us"
    }
  ],
  // Second Div Panel
  [
    {
      id: 3,
      url: "/themes-templates",
      title: "Themes & Templates"
    },
    {
      id: 4,
      url: "/open-source",
      title: "Open Source"
    },
    {
      id: 5,
      url: "/solutions",
      title: "Solutions"
    }
  ],
  // Third Div Panel
  [
    {
      id: 7,
      url: "/methodology",
      title: "Methodology",
      children: [
        {
          id: 8,
          url: "/agile",
          title: "Agile",
          children: [
            {
              id: 9,
              url: "/scrum",
              title: "Scrum"
            }
          ]
        }
      ]
    }
  ],
  // Fourth Div Panel
  [
    {
      id: 8,
      url: "/agile",
      title: "Agile",
      children: [
        {
          id: 9,
          url: "/scrum",
          title: "Scrum"
        }
      ]
    }
  ],
  // Fifth Div Panel
  [
    {
      id: 9,
      url: "/scrum",
      title: "Scrum"
    }
  ]
];
//#endregion Data

//#region Component


const PanelMenu = props => {
  const { title } = props;

  const [items, setItems] = useState(data);

  // Title Header of the Panel
  const [headerTitle, setHeaderTitle] = useState(title ? title : "");
  // Previous Title Header of the Panel
  const [prevHeaderTitle, setPrevHeaderTitle] = useState(title ? title : "");
  // ActiveIndex => 0 means by default master-panel is active
  const [activeId, setActiveId] = useState(0);
  // PreviousIndex
  const [prevId, setPrevId] = useState(0);

  const handlePanelBtn = (newTitle, index, prevIndex) => {
    // Title Checking
    const titleProp = title ? title : "";
    const prevTitle = index === 0 ? titleProp : headerTitle;
    // SetStates
    setPrevHeaderTitle(prevTitle);
    setHeaderTitle(newTitle);
    setActiveId(index);
    setPrevId(prevIndex);
  };

  const panelRenderer = () => {
    const panelsJSX = [];
    for (let i = 0; i < items.length; i++) {
      let childItemIndex = i;
      const panels = (
        <div
          key={i}
          id={i === 0 ? "p__master" : `p__student-${i}`}
          className={
            childItemIndex === activeId
              ? "p__panel is-visible"
              : "p__panel is-hide"
          }
        >
          <ul>
            {items[i].map((item, index) => {
              // It means it have children
              if (item.children && item.children.length > 0) {
                childItemIndex++;
                return (
                  <li key={item.id} className="p-next">
                    {item.url ? (
                      <a href={item.url} className="p-link">
                        {item.title}
                      </a>
                    ) : (
                      <div className="p-link">{item.title}</div>
                    )}
                    <button
                      type="button"
                      className="p-next__btn"
                      data-id={`#p__student-${childItemIndex}`}
                      onClick={() => handlePanelBtn(item.title, index, prevId)}
                    >
                      <span>&gt;</span>
                    </button>
                  </li>
                );
              } else {
                return (
                  <li key={item.id}>
                    <a href={item.url} className="p-link">
                      {item.title}
                    </a>
                  </li>
                );
              }
            })}
          </ul>
        </div>
      );

      panelsJSX.push(panels);
    }
    return panelsJSX;
  };

  const renderer = () => {
    if (items && items.length > 0) {
      return (
        <div className="p">
          <div className="p__wrap">
            {/* Panel Actions => Header */}
            <div className="p__actions">
              {/* Previous Button */}

              {activeId !== 0 && (
                <button
                  type="button"
                  className="p-action__btn left"
                  onClick={() =>
                    handlePanelBtn(prevHeaderTitle, prevId, prevId)
                  }
                >
                  <span>&lt;</span>
                </button>
              )}

              {/* Title */}
              {headerTitle && (
                <div className="p-action__title">{headerTitle}</div>
              )}

              {/* Close Button */}
              <button type="button" className="p-action__btn right">
                <span>×</span>
              </button>
            </div>
            {/* Panel children Wrapper */}
            <div className="p__children">{panelRenderer()}</div>
          </div>
        </div>
      );
    }
  };
  return <React.Fragment>{renderer()}</React.Fragment>;
};

//#endregion Component



// Render it
ReactDOM.render(
  <PanelMenu title="Menu" />,
  document.getElementById("root")
)
<style>

*,:before,:after {
    box-sizing: border-box;
}


.p__wrap {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 320px;
    background-color: #fff;
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
    z-index: 1;
    color: #333;
    overflow-x: hidden;
}

.p__actions {
    position: relative;
    padding: 14px;
    min-height: 54px;
    border-bottom: 1px solid #dcdcdc;
}

.p-action__title {
    text-align: center;
    color: #333;
    text-transform: uppercase;
    font-weight: 700;
}

.p-action__btn {
    position: absolute;
    width: 54px;
    height: 54px;
    top: 0;
    right: 0;
    font-size: 16px;
    color: #333;
    border: none;
    cursor: pointer;
}

.left {
    left: 0;
}

.right {
    right: 0;
}

.p__children {
    position: relative;
    background-color: #fff;
    overflow: hidden;
    height: calc(100% - 54px);
}

.p__panel {
    overflow-x: hidden;
    overflow-y: auto;
    position: absolute;
    transform: translateX(100%);
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 0;
    transition: transform 0.2s ease 0s;
}

.p__panel.is-visible {
    transform: translateX(0);
    z-index: 1;
}

.p__panel.is-hide {
    opacity: 0;
    visibility: hidden;
}

.p__panel > ul {
    margin: 0;
    padding: 0;
}
.p__panel > ul > li {
    list-style: none;
    border-bottom: 1px solid #dcdcdc;
}
.p__panel > ul > li > .p-link {
    color: #333;
    display: block;
    line-height: 22px;
    font-size: 14px;
    padding: 14px 24px;
    background-color: transparent;
    cursor: pointer;
}



.p__panel > ul > li > .p-link:hover {
   background-color: #dcdcdc;
}

.p-next {
    position: relative;
}

.p-next__btn {
    position: absolute;
    padding: 14px 16px;
    font-size: 16px;
    line-height: 22px;
    top: 0;
    right: 0;
    background-color: rgb(240,240,240);
    color: #333;
    border: none;
    border-left: 1px solid #dcdcdc;
    cursor: pointer;
}

</style>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

John Chucks
fonte

Respostas:

3

Fui em frente e transformei seu código em um exemplo de trabalho na seguinte caixa de proteção de código: https://codesandbox.io/s/panel-menu-hfrmx?fontsize=14&hidenavigation=1&theme=dark

Pode parecer muitas mudanças no começo, então vou elaborar um pouco:

  • Extraí o cabeçalho do menu e a lista de itens do menu em seus próprios componentes, facilitando a reutilização
  • Reescrevi sua estrutura de dados, para que você não precise definir os itens de menu duas ou três vezes. Essa estrutura é plana, o que também permite que você a armazene facilmente em um banco de dados, caso deseje.
Wouter Raateland
fonte
Vou verificar isso.
John Chucks
É uma boa idéia achatar a matriz com o pai correto indexe depois gerar esse menu. A propósito, os dados são provenientes de uma API externa. Mas usar sua abordagem é realmente útil para resolver o problema.
John Chucks 29/01
Aprecie que é útil. Se resolver o seu problema, você pode marcá-lo como aceito. Caso contrário, entre em contato se precisar de mais informações.
Wouter Raateland 29/01
Qual é a melhor abordagem para salvar a estrutura hierárquica de dados em javascript? Temos que aninhar filhos dentro de uma matriz unidimensional ou Temos que nivelar a matriz com objetos que tenham uma referência ao ID pai? O que você sugeriu?
John Chucks
Isso depende do caso de uso. Se você tiver certeza de que usará os dados apenas uma vez e se o desempenho for crítico, há algo a dizer para armazená-los em sua forma hierárquica. No entanto, se você pode usar os mesmos dados de maneiras diferentes, acho que geralmente é mais fácil armazená-lo o mais plano possível.
Wouter Raateland 29/01
1

Acho que você só precisa

handlePanelBtn(item.title, childItemIndex, prevId)

ao invés de

handlePanelBtn(item.title, index, prevId)

https://codesandbox.io/s/panel-menu-2uwxo

Squiggs.
fonte
Na sua caixa de códigos e quando clico no botão de productsitem da lista next. Não está abrindo filhos do productsitem da lista.
John Chucks
0

Eu fiz isso com a API de contexto para simplificar sua lógica de mostrar o painel.

Crie um contexto chamado PanelContext, que possui uma matriz de painéis que pode ser usada para mostrar o painel atual e voltar ao menu.

import React from "react";

export const PanelContext = React.createContext();
export function PanelProvider({ children }) {
  const [currentPanel, setCurrentPanel] = React.useState([0]);
  const addItemToPanel = item => setCurrentPanel(prev => [item, ...prev]);
  const goBack = () => setCurrentPanel(prev => prev.slice(1));
  return (
    <PanelContext.Provider
      value={{
        currentPanel: currentPanel[0],
        setCurrentPanel: addItemToPanel,
        goBack
      }}
    >
      {children}
    </PanelContext.Provider>
  );
}

e criou o componente do painel que criará recursivamente todo o painel e exibirá o painel ativo com base no valor do contexto.

const Panel = ({ items, id, title }) => {
  const { currentPanel, setCurrentPanel, goBack } = React.useContext(
    PanelContext
  );
  const panels = [];
  return (
    <>
      <div
        className={id === currentPanel ? "p__wrap visible" : " p__wrap hidden"}
      >
        <h2>
          {title && <button onClick={goBack}>{"<"}</button>} {title || "Menu"}{" "}
        </h2>
        <div className="p__panel">
          <ul>
            {items.map(item => {
              if (item.children)
                panels.push(
                  <Panel
                    title={item.title}
                    id={item.id}
                    items={item.children}
                  />
                );
              return (
                <React.Fragment key={item.id}>
                  <li>
                    {item.title}
                    {item.children && (
                      <button
                        onClick={() => {
                          setCurrentPanel(item.id);
                        }}
                      >
                        {">"}
                      </button>
                    )}
                  </li>
                </React.Fragment>
              );
            })}
          </ul>
        </div>
      </div>
      {panels}
    </>
  );
};
export const PanelMenu = props => {
  return (
    <PanelProvider>
      <Panel items={data} id={0} />
    </PanelProvider>
  );
};

Eu quebrei o seu css.

e usou apenas um objeto com filhos profundamente aninhados.

Aqui está o codesandbox em funcionamento: https://codesandbox.io/s/panel-menu-c871j

Amit Chauhan
fonte
Obrigado pela resposta realmente aprecio isso.
John Chucks
De nada!
Amit Chauhan