mirror of
https://github.com/vercel/commerce.git
synced 2025-07-21 03:41:22 +00:00
🔨 refactor: Tab Common
This commit is contained in:
parent
6895539540
commit
717ff0fa27
@ -1,22 +1,28 @@
|
||||
|
||||
@import '../../../styles/utilities';
|
||||
|
||||
.tabCommon {
|
||||
@apply flex;
|
||||
position: relative;
|
||||
border-bottom: 2px solid #FBFBFB;
|
||||
padding-top: 1.6rem;
|
||||
padding-bottom: 1.6rem;
|
||||
width: 100%;
|
||||
|
||||
.slider {
|
||||
@apply inline-block;
|
||||
height: .2rem;
|
||||
border-radius: 3px;
|
||||
background-color: var(--primary);
|
||||
position: absolute;
|
||||
z-index: 1200;
|
||||
bottom: 0;
|
||||
transition: all .4s linear;
|
||||
.tabWapper{
|
||||
@apply flex flex-col w-full;
|
||||
.tabHeader{
|
||||
@apply flex;
|
||||
.tabList {
|
||||
@apply flex;
|
||||
position: relative;
|
||||
border-bottom: 2px solid #FBFBFB;
|
||||
padding: 0.8rem 0;
|
||||
&.center{
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.slider {
|
||||
@apply inline-block;
|
||||
height: .2rem;
|
||||
border-radius: 3px;
|
||||
background-color: var(--primary);
|
||||
position: absolute;
|
||||
z-index: 1200;
|
||||
bottom: 0;
|
||||
transition: all .25s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,36 +1,85 @@
|
||||
import React, { RefObject, useEffect } from "react"
|
||||
import s from './TabCommon.module.scss'
|
||||
|
||||
import TabItem from './TabItem/TabItem'
|
||||
|
||||
interface TabCommonProps {
|
||||
tabs: {ref:RefObject<HTMLLIElement>, tabName: string, active: boolean, onClick: (tabIndex: number, tabPane?: string) => void}[];
|
||||
defaultActiveTab: number;
|
||||
sliderRef : RefObject<HTMLDivElement>;
|
||||
slideToTab: (ref: any) => void;
|
||||
}
|
||||
|
||||
const TabCommon = ({ tabs, defaultActiveTab, sliderRef, slideToTab } : TabCommonProps) => {
|
||||
|
||||
import React, {
|
||||
Children,
|
||||
PropsWithChildren,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
cloneElement,
|
||||
} from 'react'
|
||||
import s from './TabCommon.module.scss'
|
||||
|
||||
import TabItem from './components/TabItem/TabItem'
|
||||
import { TabPaneProps } from './components/TabPane/TabPane'
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface TabCommonProps {
|
||||
defaultActiveTab?: number
|
||||
children: React.ReactNode
|
||||
center?:boolean
|
||||
}
|
||||
|
||||
const TabCommon = ({
|
||||
defaultActiveTab = 0,
|
||||
children,
|
||||
center
|
||||
}: TabCommonProps) => {
|
||||
const [active, setActive] = useState(0)
|
||||
const slider = useRef<HTMLDivElement>(null)
|
||||
const headerRef = useRef<HTMLUListElement>(null)
|
||||
useEffect(() => {
|
||||
slideToTab(tabs[defaultActiveTab].ref);
|
||||
setActive(defaultActiveTab)
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
slide(active)
|
||||
}, [active])
|
||||
|
||||
function slide(index: number) {
|
||||
const active = headerRef.current?.children
|
||||
.item(index)
|
||||
?.getBoundingClientRect()
|
||||
const header = headerRef.current?.getBoundingClientRect()
|
||||
const current = slider.current
|
||||
if (current && active && header) {
|
||||
let width = active.width - 24 <= 0 ? 24 : active.width - 24
|
||||
let left = active.left - header.left
|
||||
current.style.width = width.toString() + 'px'
|
||||
current.style.left = left.toString() + 'px'
|
||||
}
|
||||
}
|
||||
const onTabClick = (index: number) => {
|
||||
setActive(index)
|
||||
}
|
||||
return (
|
||||
<ul className={s.tabCommon}>
|
||||
{
|
||||
tabs.map((tab) => {
|
||||
return (
|
||||
<li key={tab.tabName} ref={tab.ref}>
|
||||
<TabItem onClick={tab.onClick} active={tab.active}>{tab.tabName}</TabItem>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
<div ref={sliderRef} className={s.slider}></div>
|
||||
</ul>
|
||||
<section className={s.tabWapper}>
|
||||
<div className={s.tabHeader}>
|
||||
<ul className={classNames(s.tabList,{[s.center]:center})} ref={headerRef}>
|
||||
{Children.map(children, (tab, index) => {
|
||||
let item = tab as ReactElement<PropsWithChildren<TabPaneProps>>
|
||||
return (
|
||||
<li key={item.props.tabName}>
|
||||
<TabItem
|
||||
active={active === index}
|
||||
onClick={onTabClick}
|
||||
tabIndex={index}
|
||||
>
|
||||
{item.props.tabName}
|
||||
</TabItem>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
<div ref={slider} className={s.slider}></div>
|
||||
</ul>
|
||||
</div>
|
||||
<div className={s.tabBody}>
|
||||
{Children.map(children, (tab, index) => {
|
||||
let item = tab as ReactElement<PropsWithChildren<TabPaneProps>>
|
||||
return cloneElement(item, { active:index===active });
|
||||
})
|
||||
}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabCommon;
|
||||
}
|
||||
|
||||
export default TabCommon
|
@ -0,0 +1,13 @@
|
||||
@import '../../../../../styles/utilities';
|
||||
|
||||
.tabItem {
|
||||
margin-right:2.4rem;
|
||||
padding: 0.8rem 0;
|
||||
min-width: 2.4rem;
|
||||
&:hover {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
&.tabItemActive {
|
||||
@apply font-bold;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
import s from './TabItem.module.scss'
|
||||
|
||||
interface TabItemProps {
|
||||
active: boolean
|
||||
children: string
|
||||
onClick?: (tabIndex: number) => void
|
||||
tabIndex: number
|
||||
}
|
||||
|
||||
const TabItem = ({
|
||||
active = false,
|
||||
children,
|
||||
onClick,
|
||||
tabIndex,
|
||||
}: TabItemProps) => {
|
||||
const handleClick = () => {
|
||||
onClick && onClick(tabIndex)
|
||||
}
|
||||
return (
|
||||
<span
|
||||
onClick={handleClick}
|
||||
className={classNames(s.tabItem, {[s.tabItemActive]:active})}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabItem
|
@ -0,0 +1,23 @@
|
||||
@import "../../../../../styles/utilities";
|
||||
.tabPane {
|
||||
@apply hidden;
|
||||
transition: all 0.6s;
|
||||
// animation-duration: 0.6s;
|
||||
// animation-name: appear;
|
||||
// @keyframes appear {
|
||||
// from {
|
||||
// margin-left: 100%;
|
||||
// width: 200%;
|
||||
// }
|
||||
|
||||
// to {
|
||||
// margin-left: 0%;
|
||||
// width: 100%;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
&.active {
|
||||
@apply block;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import classNames from "classnames"
|
||||
import React from "react"
|
||||
import s from './TabPane.module.scss'
|
||||
|
||||
export interface TabPaneProps {
|
||||
active?: boolean;
|
||||
children?: React.ReactNode;
|
||||
tabName: string
|
||||
}
|
||||
|
||||
const TabPane = ({ active, children } : TabPaneProps) => {
|
||||
return (
|
||||
<section className={classNames(s.tabPane, {
|
||||
[s.active] : active
|
||||
})}>
|
||||
{children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabPane
|
Loading…
x
Reference in New Issue
Block a user