용도
다수의 메뉴 박스처럼 데이터 리스트를 접어서 정리할 때 유용한 ui가 아코디언이다.
해당 ui는 react와 reset css, css의 class 기반 스타일링으로 이뤄졌다.
아래에 소스코드를 첨부했으니 필요하다면 사용하면 된다.
app.tsx
import Accordion from "./components/accordion/Accordion";
// 아코디언 ui용 임시 데이터
const menu = [
{
id: "00",
label: "에피타이저",
},
{
id: "01",
label: "메인 음식",
},
{
id: "02",
label: "디저트",
},
{
id: "03",
label: "음료",
},
{
id: "04",
label: "셀러드",
},
{
id: "05",
label: "일식",
},
{
id: "06",
label: "중식",
},
{
id: "07",
label: "한식",
},
];
const children = [1, 2, 3, 4, 5, 6, 7].map((e) => (
<div>
<h3>item{e}</h3>
<p>
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Necessitatibus
aliquid perferendis cupiditate esse optio rem facilis. Accusamus numquam
eveniet asperiores eaque provident dicta itaque placeat, debitis fugit
maiores. Quam, cum.
</p>
</div>
));
//
function App() {
return (
<div id="app">
// 아코디언 ui
<Accordion items={menu}>{children}</Accordion>
</div>
);
}
export default App;
Accodion.tsx
아코디언 기능의 핵심은 item인 패널을 열고 닫을 때 스타일링 제어이다.
나의 경우, max-height:0;과 max-height:scrollHeight;를 사용했다.
일반적인 height보다 애니메이션 시 리소스를 좀 더 적게 먹는다는 이야기를 gpt로 들어서인데,
따로 확인한 레퍼런스는 존재하지 않는다.
import "../accordion/accordionStyle.css";
export interface accodionItem {
id: string;
label: string;
}
interface AccodionProps {
// 아코디언 패널
items: accodionItem[];
// 패널의 내용이 되는 컴포넌트
children: React.ReactNode;
// 외부에서 동적 스타일링 시
dom?: {
layout?: {
className?: string;
style?: {
[key: string]: React.CSSProperties;
};
[key: string]: string | React.CSSProperties | undefined;
};
};
}
const Accodion = ({ items, dom, children }: AccodionProps) => {
// 패널 클릭 시 열고 닫음
// 패널의 콘텐츠를 클릭하는 경우에는 예외로 무시
const handleClickPanelOpen = (e: React.MouseEvent<HTMLUListElement>) => {
const target = e.target as HTMLLIElement;
// 패널 콘텐츠
const closet = target.closest('[data-istoggle="false"]');
if (closet) return;
const pannel = target.closest("[data-type=pannel]") as HTMLLIElement;
if (pannel && typeof Number(pannel.dataset.pannelIndex) === "number") {
const contentWrapper = pannel.querySelector(
".accordion__pannel-content-wrapper"
) as HTMLLIElement;
//pannel-open 클래스로 패널 열림 제어
pannel.classList.toggle("pannel-open");
if (contentWrapper && pannel.classList.contains("pannel-open")) {
contentWrapper.style.maxHeight = contentWrapper.scrollHeight + "px";
} else {
contentWrapper.style.maxHeight = "0";
}
}
};
return (
<div className="accordion__layout" style={dom?.layout?.style}>
<ul
className="accordion__pannel-wrapper scroll"
onClick={handleClickPanelOpen}
>
{items.map((pannel, pannelIdx) => {
return (
<li
data-type={"pannel"}
data-pannel-index={pannelIdx}
className="accordion__pannel"
key={pannel.id}
>
<div className="accordion__pannel-header">
<div>
<span className="accordion__pannel-title"></span>{" "}
{pannel.label}
</div>
{/* 화살표 아이콘 */}
<div className="accordion__icon-wrapper">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
className="icon-path"
d="M3 5.90446L3.8875 5L8 9.19108L12.1125 5L13 5.90446L8 11L3 5.90446Z"
fill="#777777"
/>
</svg>
</div>
</div>
<div className="accordion__pannel-content-wrapper scroll">
<div
className="accordion__pannel-content"
data-istoggle="false"
>
{Array.isArray(children)
? children?.[pannelIdx]
: [children][pannelIdx]}
</div>
</div>
</li>
);
})}
</ul>
</div>
);
};
export default Accodion;
accordionStyle.css
/* Layout */
.accordion__layout {
background-color: rgb(231, 235, 241);
width: 100%;
padding: 20px 0px;
height: 100%;
border-radius: 10px;
}
/* Pannel Wrapper */
.accordion__pannel-wrapper {
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
gap: 10px;
padding: 0 10px;
margin: 0 5px;
}
/*Pannel */
.accordion__pannel {
cursor: pointer;
display: flex;
flex-direction: column;
background-color: #ffffff;
border: 1px solid transparent;
border-radius: 10px;
color: #000000;
margin-bottom: 5px;
padding: 10px;
box-shadow: 2px 2px 4px 0px #0000001a;
transition: 0.2s all ease-in-out;
}
/* .pannel-open: 패널이 열릴 때 */
.accordion__pannel:not(.pannel-open):hover {
filter: brightness(0.98);
}
.accordion__pannel.pannel-open {
border: 1px solid #397de2;
color: #0b52be;
background-color: #f5faff;
}
/* Pannel Content Wrapper */
.accordion__pannel-content-wrapper {
overflow-y: hidden;
max-height: 0;
transition: all 0.2s ease-in-out;
}
.accordion__pannel.pannel-open .accordion__pannel-content-wrapper {
/* max-height: 600px; */
}
/* Pannel Content */
.accordion__pannel-content {
cursor: auto;
color: black;
font-size: 12px;
}
.accordion__pannel.pannel-open .accordion__pannel-content {
}
/* Icon Wrapper */
.accordion__icon-wrapper {
display: flex;
justify-content: center;
align-items: center;
left: 0px;
height: 100%;
margin-left: 10px;
transition: all 0.1s ease-in-out;
}
.accordion__pannel.pannel-open .accordion__icon-wrapper {
transform: rotate(180deg);
}
.accordion__icon-wrapper path {
fill: initial;
}
.accordion__pannel.pannel-open .accordion__icon-wrapper path {
fill: #0864f0;
}
/* Pannel Header */
.accordion__pannel-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin: 10px 0;
font-size: 12px;
color: rgb(80, 80, 80);
font-weight: 600;
}
.pannel-open .accordion__pannel-header {
color: #0b52be;
}
.accordion__pannel-title {
margin-left: 5px;
}
.scroll {
}
.scroll::-webkit-scrollbar {
width: 6px;
height: 6px;
padding: 2px;
margin: 2px;
}
.scroll::-webkit-scrollbar-thumb {
background-color: #a3a3a3;
border-radius: 10px;
}
'CSS > 공용 ui' 카테고리의 다른 글
[css, 공용 ui, js] max-height, max-width로 접었다 피는 폴딩 ui 반응형 만들기(% 단위를 사용하지 못할 때) (0) | 2024.09.21 |
---|---|
[css] ul, ol 태그 공부 (+: custom- ul 만들기) (0) | 2024.08.15 |