이전에 블로그에 적었던 버튼 클릭 후 원하는 컴포넌트를 렌더링 하는 방법을 고도화했어요
- Headless 기반으로 제작하여 원하는 방식으로 스타일을 지정할 수 있도록 대응
- 키보드 이벤트를 추가해서 화살표 이동에 따라 TabItem이 Focus가 되어 해당 Tab의 값에 따라 원하는 컴포넌트를 렌더링
TabItem.tsx
import React, {
ButtonHTMLAttributes,
forwardRef,
useEffect,
useRef,
} from "react";
interface TabItemProps<T> extends ButtonHTMLAttributes<HTMLButtonElement> {
tabValue: T;
onFocusItem?: (item: T) => void;
}
const TabItem = forwardRef<HTMLButtonElement, TabItemProps<any>>(
(props, ref) => {
const buttonRef = useRef<HTMLButtonElement>();
const { tabValue, onFocusItem, onFocus, onKeyDown, ...restProps } = props;
useEffect(() => {
buttonRef.current?.focus();
}, []);
return (
<button
ref={(node) => {
if (node) {
buttonRef.current = node;
}
if (ref) {
if (typeof ref === "function") {
ref(node);
} else {
ref.current = node;
}
}
}}
onFocus={(e) => {
onFocusItem?.(tabValue);
onFocus?.(e);
}}
role="tab"
onKeyDown={(e) => {
if (buttonRef.current) {
if (e.key === "ArrowRight") {
const next = buttonRef.current.nextSibling as HTMLButtonElement;
if (next) {
next.focus();
}
}
if (e.key === "ArrowLeft") {
const prev = buttonRef.current
.previousSibling as HTMLButtonElement;
if (prev) {
prev.focus();
}
}
}
onKeyDown?.(e);
}}
{...restProps}
/>
);
}
);
export default TabItem;
import React, { useRef, useState } from "react";
import styled, { css } from "styled-components";
import TabItem from "../../components/atoms/TabItem";
type TabType = "NAME" | "ADDRESS" | "AGE";
const TAB_LIST: TabType[] = ["NAME", "ADDRESS", "AGE"];
function Tabs() {
const buttonRef = useRef<HTMLButtonElement>(null);
const [selectTab, setSelectTab] = useState<TabType>("NAME");
const renderTabItemTitle = (type: TabType) => {
switch (type) {
case "NAME":
return <span>Name</span>;
case "ADDRESS":
return <span>Address</span>;
case "AGE":
return <span>Age</span>;
}
};
const renderTabItemContent = (tabType: TabType) => {
switch (tabType) {
case "NAME":
return <>Name</>;
case "ADDRESS":
return <>Address</>;
case "AGE":
return <>Age</>;
}
};
return (
<>
<StyledTabs role="tablist">
{TAB_LIST.map((item, idx) => {
return (
<StyeldTabItem
ref={item === selectTab ? buttonRef : null}
key={idx.toString()}
tabIndex={item === selectTab ? 0 : -1}
isSelect={item === selectTab}
tabValue={item}
onFocusItem={(value) => {
setSelectTab(value as TabType);
}}
>
{renderTabItemTitle(item)}
</StyeldTabItem>
);
})}
</StyledTabs>
{renderTabItemContent(selectTab)}
</>
);
}
export default Tabs;
const StyledTabs = styled.nav`
display: flex;
margin: 24px 0;
border-bottom: 1px solid gray;
`;
const StyeldTabItem = styled(TabItem)<{
isSelect?: boolean;
}>`
display: flex;
justify-content: center;
align-items: center;
flex: 1;
padding: 8px 16px;
cursor: pointer;
background: none;
box-shadow: none;
${({ isSelect }) => {
return isSelect
? css`
border: none;
border-bottom: 3px solid red;
`
: css`
border: none;
`;
}}
`;
키보드 화살표 leftArrow
, rightArrow
와 원하는 Tab 클릭시
하단의 원하는 컴포넌트를 렌더링 시킬 수 있습니다!