我想做的是用箭头按钮使侧边栏折叠,当前代码使按钮折叠并展开侧边栏,但是它的位置错误,因为它应该附加到侧边栏并且当前出现在中间屏幕的。它还应该有一个到侧边栏的路径,因为它没有。最后,当侧边栏折叠时,顶部的徽标应该更改为较小的版本。
代码、jsx 和 css
import React, { useState } from 'react';
import { SideBarWrapper, Arrow, ToggleButtonWrapper } from './Sidebar.styles';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
interface Section {
kms: number;
sseccion: string;
bedicion: boolean;
}
interface Module {
kmodulo: number;
smodulo: string;
secciones: Section[];
}
const Sidebar: React.FC = () => {
const [selectedModule, setSelectedModule] = useState<string | null>(null);
const [isSidebarOpen, setSidebarOpen] = useState(true);
const toggleExpand = (moduleName: string) => {
setSelectedModule((prev) => (prev === moduleName ? null : moduleName));
};
const handleBarsMenuClick = () => {
setSidebarOpen(!isSidebarOpen);
};
const modules: Module[] = [
{
kmodulo: 3,
smodulo: 'Laboratorio',
secciones: [
{ kms: 10, sseccion: 'Atención a clientes', bedicion: true },
{ kms: 15, sseccion: 'Caja', bedicion: true },
{ kms: 14, sseccion: 'Paciente 360', bedicion: true },
],
},
{
kmodulo: 3,
smodulo: 'Sucursales',
secciones: [
{ kms: 10, sseccion: 'Caja - Cobro', bedicion: true },
{ kms: 15, sseccion: 'Clientes', bedicion: true },
{ kms: 14, sseccion: 'Convenios', bedicion: true },
{ kms: 13, sseccion: 'Listas de Precio', bedicion: true },
{ kms: 11, sseccion: 'Médicos', bedicion: true },
{ kms: 12, sseccion: 'Pacientes', bedicion: true },
],
},
{
kmodulo: 3,
smodulo: 'Recursos Humanos',
secciones: [
{ kms: 10, sseccion: 'Caja - Cobro', bedicion: true },
{ kms: 15, sseccion: 'Clientes', bedicion: true },
{ kms: 14, sseccion: 'Convenios', bedicion: true },
{ kms: 13, sseccion: 'Listas de Precio', bedicion: true },
{ kms: 11, sseccion: 'Médicos', bedicion: true },
{ kms: 12, sseccion: 'Pacientes', bedicion: true },
],
},
];
return (
<SideBarWrapper isOpen={isSidebarOpen}>
<div className="sidebar-header">
<a className="nav-link" href="/dashboard/">
<img src="/img/Logo_Completo_Dark.png" alt="Logo Medvana" height="40px" />
</a>
</div>
{isSidebarOpen &&
modules.map((module, index) => (
<div key={index}>
<div
className={`menu-item ${selectedModule === module.smodulo ? 'selected' : ''}`}
onClick={() => toggleExpand(module.smodulo)}
>
<div className="menu-title">{module.smodulo}</div>
<Arrow isExpanded={selectedModule === module.smodulo} />
</div>
{selectedModule === module.smodulo && (
<div className="submenu">
{module.secciones.map((seccion, sIndex) => (
<div className="submenu-item" key={sIndex}>
{seccion.sseccion}
</div>
))}
</div>
)}
</div>
))}
<ToggleButtonWrapper onClick={handleBarsMenuClick}>
<FontAwesomeIcon icon={isSidebarOpen ? faChevronLeft : faChevronRight} />
</ToggleButtonWrapper>
</SideBarWrapper>
);
};
export default Sidebar;
import styled from 'styled-components';
interface SideBarWrapperProps {
isOpen: boolean;
}
export const SideBarWrapper = styled.div<SideBarWrapperProps>`
width: ${(props) => (props.isOpen ? '280px' : '80px')}; /* Cambia el ancho al colapsar */
height: 100vh; /* Ajustamos a la altura de la ventana */
background-color: #002856;
color: #fff;
padding: 10px;
display: flex;
flex-direction: column;
transition: width 0.3s ease; /* Transición suave cuando colapse */
.sidebar-header {
display: flex;
justify-content: center;
align-items: center;
padding-top: 20px;
padding-bottom: 40px;
margin-top: 10px;
position: relative;
img {
height: 40px;
width: auto;
}
&::after {
content: '';
position: absolute;
bottom: 0;
width: ${(props) => (props.isOpen ? '95%' : '60%')}; /* Ajustar línea de separación */
height: 2px;
margin-bottom: 18px;
background-color: #ffffff33;
}
}
.menu-item {
width: 100%;
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
background-color: #002856;
border-radius: 8px;
margin-left: 6px;
padding-right: ${(props) => (props.isOpen ? '12px' : '0px')}; /* Ajustar el padding cuando colapse */
&:hover,
&:active {
background-color: #235a98;
}
&.selected {
background-color: #235a98;
}
}
.menu-title {
font-size: ${(props) => (props.isOpen ? '16px' : '0px')}; /* Ocultar el título al colapsar */
font-weight: bold;
padding: 8px 16px;
white-space: nowrap;
}
.submenu {
width: 100%;
margin-top: 10px;
padding-left: 50px;
display: flex;
flex-direction: column;
gap: 10px;
}
.submenu-item {
width: 100%;
height: 36px;
padding: 8px 50px 8px 8px;
border-radius: 8px;
display: flex;
align-items: center;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
background-color: #ffffff33;
}
}
`;
interface ArrowProps {
isExpanded: boolean;
}
export const Arrow = styled.i<ArrowProps>`
width: 10px;
height: 10px;
display: flex;
transition: transform 0.3s ease;
border: solid white;
border-width: 0 3px 3px 0;
padding: 3px;
transform: ${(props) => (props.isExpanded ? 'rotate(-135deg)' : 'rotate(45deg)')};
margin-left: auto;
margin-right: 10px;
`;
export const ToggleButtonWrapper = styled.div`
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: #6a0dad; /* Color morado */
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
svg {
color: white;
font-size: 20px;
}
&:hover {
background-color: #5b099d; /* Un tono más oscuro de morado */
}
`;
有一个来自样式组件的警告(看起来有一个未知的 prop 正在被发送到 DOM),因此我在需要的变量中添加了供应商前缀 ($)。您可以在此线程中阅读有关警告的信息:https://github.com/styled-components/styled-components/issues/4071。
关于箭头按钮解决方案:将 position: relative
添加到父元素(即 SideBarWrapper 组件),解决了该问题。
解释:本线程中的第一个答案解释了如何很好地定位父母和孩子的相互关系:绝对定位但相对于父母
关于解决方案及解释:我有条件地渲染图像的高度,使用isOpen状态,这里用它来监控SideBarWrapper
组件的状态。另外还为图像添加了过渡,使尺寸变化无缝:
img {
height: ${({ $isOpen }) => ($isOpen ? "40px" : "20px")};
width: auto;
transition: height 0.3s ease;
}
最终代码如下所示:JSX:
import React, { useState } from "react";
import {
SideBarWrapper,
Arrow,
ToggleButtonWrapper,
} from './Sidebar.styles';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faChevronLeft,
faChevronRight,
} from "@fortawesome/free-solid-svg-icons";
interface Section {
kms: number;
sseccion: string;
bedicion: boolean;
}
interface Module {
kmodulo: number;
smodulo: string;
secciones: Section[];
}
const Sidebar: React.FC = () => {
const [selectedModule, setSelectedModule] = useState<string | null>(null);
const [isSidebarOpen, setSidebarOpen] = useState(true);
const toggleExpand = (moduleName: string) => {
setSelectedModule((prev) => (prev === moduleName ? null : moduleName));
};
const handleBarsMenuClick = () => {
setSidebarOpen(!isSidebarOpen);
};
const modules: Module[] = [
{
kmodulo: 3,
smodulo: "Laboratorio",
secciones: [
{ kms: 10, sseccion: "Atención a clientes", bedicion: true },
{ kms: 15, sseccion: "Caja", bedicion: true },
{ kms: 14, sseccion: "Paciente 360", bedicion: true },
],
},
{
kmodulo: 3,
smodulo: "Sucursales",
secciones: [
{ kms: 10, sseccion: "Caja - Cobro", bedicion: true },
{ kms: 15, sseccion: "Clientes", bedicion: true },
{ kms: 14, sseccion: "Convenios", bedicion: true },
{ kms: 13, sseccion: "Listas de Precio", bedicion: true },
{ kms: 11, sseccion: "Médicos", bedicion: true },
{ kms: 12, sseccion: "Pacientes", bedicion: true },
],
},
{
kmodulo: 3,
smodulo: "Recursos Humanos",
secciones: [
{ kms: 10, sseccion: "Caja - Cobro", bedicion: true },
{ kms: 15, sseccion: "Clientes", bedicion: true },
{ kms: 14, sseccion: "Convenios", bedicion: true },
{ kms: 13, sseccion: "Listas de Precio", bedicion: true },
{ kms: 11, sseccion: "Médicos", bedicion: true },
{ kms: 12, sseccion: "Pacientes", bedicion: true },
],
},
];
return (
<SideBarWrapper $isOpen={isSidebarOpen}>
<div className="sidebar-header">
<a className="nav-link" href="/dashboard/">
<img src=""/img/Logo_Completo_Dark.png"" alt="Logo Medvana" />
</a>
</div>
{isSidebarOpen &&
modules.map((module, index) => (
<div key={index}>
<div
className={`menu-item ${
selectedModule === module.smodulo ? "selected" : ""
}`}
onClick={() => toggleExpand(module.smodulo)}
>
<div className="menu-title">{module.smodulo}</div>
<Arrow $isExpanded={selectedModule === module.smodulo} />
</div>
{selectedModule === module.smodulo && (
<div className="submenu">
{module.secciones.map((seccion, sIndex) => (
<div className="submenu-item" key={sIndex}>
{seccion.sseccion}
</div>
))}
</div>
)}
</div>
))}
<ToggleButtonWrapper onClick={handleBarsMenuClick}>
<FontAwesomeIcon
icon={isSidebarOpen ? faChevronLeft : faChevronRight}
/>
</ToggleButtonWrapper>
</SideBarWrapper>
);
};
export default Sidebar;
CSS:
import styled from "styled-components";
interface SideBarWrapperProps {
$isOpen: boolean;
}
export const SideBarWrapper = styled.div<SideBarWrapperProps>`
width: ${({ $isOpen }) =>
$isOpen ? "280px" : "80px"}; /* Cambia el ancho al colapsar */
height: 100vh; /* Ajustamos a la altura de la ventana */
background-color: #002856;
color: #fff;
padding: 10px;
display: flex;
flex-direction: column;
position: relative;
transition: width 0.3s ease; /* Transición suave cuando colapse */
.sidebar-header {
display: flex;
justify-content: center;
align-items: center;
padding-top: 20px;
padding-bottom: 40px;
margin-top: 10px;
position: relative;
img {
height: ${({ $isOpen }) => ($isOpen ? "40px" : "20px")};
width: auto;
transition: height 0.3s ease;
}
&::after {
content: "";
position: absolute;
bottom: 0;
width: ${({ $isOpen }) =>
$isOpen ? "95%" : "60%"}; /* Ajustar línea de separación */
height: 2px;
margin-bottom: 18px;
background-color: #ffffff33;
}
}
.menu-item {
width: 100%;
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
background-color: #002856;
border-radius: 8px;
margin-left: 6px;
padding-right: ${({ $isOpen }) =>
$isOpen ? "12px" : "0px"}; /* Ajustar el padding cuando colapse */
&:hover,
&:active {
background-color: #235a98;
}
&.selected {
background-color: #235a98;
}
}
.menu-title {
font-size: ${({ $isOpen }) =>
$isOpen ? "16px" : "0px"}; /* Ocultar el título al colapsar */
font-weight: bold;
padding: 8px 16px;
white-space: nowrap;
}
.submenu {
width: 100%;
margin-top: 10px;
padding-left: 50px;
display: flex;
flex-direction: column;
gap: 10px;
}
.submenu-item {
width: 100%;
height: 36px;
padding: 8px 50px 8px 8px;
border-radius: 8px;
display: flex;
align-items: center;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
background-color: #ffffff33;
}
}
`;
interface ArrowProps {
$isExpanded: boolean;
}
export const Arrow = styled.i<ArrowProps>`
width: 10px;
height: 10px;
display: flex;
transition: transform 0.3s ease;
border: solid white;
border-width: 0 3px 3px 0;
padding: 3px;
transform: ${({ $isExpanded }) =>
$isExpanded ? "rotate(-135deg)" : "rotate(45deg)"};
margin-left: auto;
margin-right: 10px;
`;
export const ToggleButtonWrapper = styled.div`
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: #6a0dad; /* Color morado */
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
svg {
color: white;
font-size: 20px;
}
&:hover {
background-color: #5b099d; /* Un tono más oscuro de morado */
}
`;
如果您需要更多帮助,请随时询问。