import React, { HTMLAttributes, ReactElement, ReactNode, useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import classNames from 'classnames'; type VNode = ReactElement & { type: { name: string } }; export interface TabsProps extends HTMLAttributes { bgc?: string; position?: 'center' | 'start' | 'end'; children?: React.ReactNode; callback?: (key: string) => void; defaultKey?: string; gap?: number; segmented?: boolean; } type TabNameProp = { bgc?: string; position?: 'center' | 'start' | 'end'; gap?: number; }; const TabsStyled = styled.div` width: 100%; height: 100%; display: flex; flex-direction: column; gap: ${(props: TabNameProp) => `${props.gap}px`}; `; const TabName = styled.div` height: 40px; position: relative; cursor: pointer; display: flex; gap: 5vw; justify-content: ${(props: TabNameProp) => props.position}; //border-bottom: 1px solid rgba(0, 0, 0, 0.1); &.segmented { gap: 0; height: 35px; border-bottom: none; background-color: #ededed; box-shadow: 4px 4px 1px 1px rgba(237, 237, 237, 1), -4px 4px 1px 1px rgba(237, 237, 237, 1), 4px -4px 1px 1px rgba(237, 237, 237, 1), -4px -4px 1px 1px rgba(237, 237, 237, 1); > span.title { height: 100%; display: flex; align-items: center; padding: 6px 15px; } > span.indicator { height: 100%; background-color: #ffffff; } } > span.title { height: 100%; padding: 10px 0; position: relative; z-index: 10; color: #262626; user-select: none; } > span.indicator { position: absolute; bottom: 0; height: 3px; transition: all 250ms; background-color: ${(props: TabNameProp) => props.bgc}; } `; const Content = styled.div``; const Tabs: React.FC = (props) => { const { children, callback, gap, segmented, position, bgc, defaultKey, ...rest } = props; const [currentIndex, setIndex] = useState(defaultKey!); const spanRef = useRef([]); const lastSpan = useRef(null); const spanWrap = useRef(null); const indicator = useRef(null); useEffect(() => { Array.from(spanWrap.current!.children).forEach((item) => { if (item.getAttribute('data-order') === currentIndex) { if (lastSpan.current) lastSpan.current.style.color = '#262626'; const el = item as HTMLSpanElement; lastSpan.current = el; el.style.color = bgc!; const { width, left: left1 } = el.getBoundingClientRect(); const { left: left2 } = spanWrap.current!.getBoundingClientRect(); indicator.current!.style.width = `${width}px`; indicator.current!.style.left = `${left1 - left2}px`; } }); }); const tabClick = (e: React.MouseEvent, index: string) => { callback!(index); setIndex(index); const el = e.target as HTMLSpanElement; const { width, left: left1 } = el.getBoundingClientRect(); const { left: left2 } = spanWrap.current!.getBoundingClientRect(); indicator.current!.style.width = `${width}px`; indicator.current!.style.left = `${left1 - left2}px`; }; const render = () => { let currentVNode!: VNode; React.Children.map(children, (child) => { const vNode = child as VNode; if (React.isValidElement(vNode) && vNode.props.index === currentIndex) { currentVNode = vNode; spanRef.current.push( ) => tabClick(e, vNode.props.index)} data-order={vNode.props.index} > {vNode.props.tab} ); } else if (React.isValidElement(vNode) && vNode.props.index !== currentIndex) { spanRef.current.push( ) => tabClick(e, vNode.props.index)} data-order={vNode.props.index} > {vNode.props.tab} ); } }); return ( <> {spanRef.current} {currentVNode} ); }; return ( {render()} ); }; Tabs.defaultProps = { callback: () => {}, defaultKey: '1', children: '', bgc: '#1890ff', position: 'start', gap: 30, segmented: false }; export default Tabs;