import type { JSX } from 'react' import { cloneElement, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../base/portal-to-follow-elem' import { RiMoreLine } from '@remixicon/react' export type Operation = { id: string title: string icon: JSX.Element onClick: () => void type?: 'divider' } type AppOperationsProps = { gap: number operations?: Operation[] primaryOperations?: Operation[] secondaryOperations?: Operation[] } const EMPTY_OPERATIONS: Operation[] = [] const AppOperations = ({ operations, primaryOperations, secondaryOperations, gap, }: AppOperationsProps) => { const { t } = useTranslation() const [visibleOpreations, setVisibleOperations] = useState([]) const [moreOperations, setMoreOperations] = useState([]) const [showMore, setShowMore] = useState(false) const navRef = useRef(null) const handleTriggerMore = useCallback(() => { setShowMore(true) }, [setShowMore]) const primaryOps = useMemo(() => { if (operations) return operations if (primaryOperations) return primaryOperations return EMPTY_OPERATIONS }, [operations, primaryOperations]) const secondaryOps = useMemo(() => { if (operations) return EMPTY_OPERATIONS if (secondaryOperations) return secondaryOperations return EMPTY_OPERATIONS }, [operations, secondaryOperations]) const inlineOperations = primaryOps.filter(operation => operation.type !== 'divider') useEffect(() => { const applyState = (visible: Operation[], overflow: Operation[]) => { const combinedMore = [...overflow, ...secondaryOps] if (!overflow.length && combinedMore[0]?.type === 'divider') combinedMore.shift() setVisibleOperations(visible) setMoreOperations(combinedMore) } const inline = primaryOps.filter(operation => operation.type !== 'divider') if (!inline.length) { applyState([], []) return } const navElement = navRef.current const moreElement = document.getElementById('more-measure') if (!navElement || !moreElement) return let width = 0 const containerWidth = navElement.clientWidth const moreWidth = moreElement.clientWidth if (containerWidth === 0 || moreWidth === 0) return const updatedEntries: Record = inline.reduce((pre, cur) => { pre[cur.id] = false return pre }, {} as Record) const childrens = Array.from(navElement.children).slice(0, -1) for (let i = 0; i < childrens.length; i++) { const child = childrens[i] as HTMLElement const id = child.dataset.targetid if (!id) break const childWidth = child.clientWidth if (width + gap + childWidth + moreWidth <= containerWidth) { updatedEntries[id] = true width += gap + childWidth } else { if (i === childrens.length - 1 && width + childWidth <= containerWidth) updatedEntries[id] = true else updatedEntries[id] = false break } } const visible = inline.filter(item => updatedEntries[item.id]) const overflow = inline.filter(item => !updatedEntries[item.id]) applyState(visible, overflow) }, [gap, primaryOps, secondaryOps]) const shouldShowMoreButton = moreOperations.length > 0 return ( <>
{visibleOpreations.map(operation => ( ))} {shouldShowMoreButton && (
{moreOperations.map(item => item.type === 'divider' ? (
) : (
{cloneElement(item.icon, { className: 'h-4 w-4 text-text-tertiary' })} {item.title}
))}
)}
) } export default AppOperations