import React, { ForwardedRef, forwardRef, ElementType, ElementRef, useId, cloneElement, ReactElement, useRef, MouseEventHandler, MouseEvent, useMemo, } from 'react'; import { RtSpinner } from '@dpa/react-loaders'; import { RtItemProps } from '../types/rt-item.types'; import { RtText } from '@dpa/react-text'; import { useRtBaseProps } from '@dpa/react-hoc'; import styles from '../styles/rt-item.module.css'; import classNames from 'classnames/bind'; import { itemIconSizeMap, itemSizeTypographyMap } from '../constants'; import { RtSymbolAction } from '@dpa/react-symbol-action'; import mergeRefs from 'merge-refs'; import { RtGridChildContext, useGridChild } from '@dpa/react-grid'; import { RtFrame } from '@dpa/react-frame-intro'; import { getSurfaceState } from '../utils/get-surface-state'; import config from '../styles/item-config.module.css'; import { useDndItem } from '../hooks/useDndItem'; import { getSlotInfo } from '../utils/get-slot-info'; import { createClickEvent } from '../utils/create-click-event'; const cn = classNames.bind(styles); const selectorComponents = ['RtToggle', 'RtRadio', 'RtCheckbox']; const stopPropagation = ( e: MouseEvent, fn?: (() => void) | false ) => { e.stopPropagation(); if (fn) fn?.(); }; export const RtInternalItem = ( props: RtItemProps, ref: ForwardedRef> ) => { const { as = 'div', disabled, leadingIcon: LeadingIcon, leadingSlot, trailingIcon: TrailingIcon, trailingSlot, variant = 'normal', labelSlot, action, actionSymbol, actionSlot, actionLabel, actionIcon = true, subLabelSpecification = 'main', selectorSlot, selectorReverse = false, label, subLabel, size = 'md', onActionClick, onClear, readOnly, readonlyEffectState, readonlyEffectVariant, labelLineClamp, subLabelLineClamp, cursor, squeeze = false, ...otherProps } = props; const { onClick, id: propsId, ...baseProps } = useRtBaseProps(props, '0.2.2'); const TrailingAction = action || RtSymbolAction.Chevron; const localId = useId(); const labelId = useId(); const id = propsId || localId; const localRef = useRef(null); const isSelector = useMemo(() => { return selectorComponents.includes(getSlotInfo(selectorSlot)?.displayName); }, [selectorSlot]); const cursorComputed = cursor ? cursor : isSelector ? 'pointer' : 'default'; const isNormal = variant === 'normal'; const SelectorSlot = selectorSlot && selectorComponents.includes(getSlotInfo(selectorSlot)?.displayName) ? cloneElement(selectorSlot as ReactElement, { ariaLabelledBy: labelId, modifier: selectorSlot?.props?.modifier || 'base', }) : selectorSlot; const { gridChildClassname, gridAttributes } = useGridChild({ size, radius: 'primary', }); const { attributes, listeners, setNodeRef, style, draggingStyle } = useDndItem(id); const handleClick: MouseEventHandler = (e) => { if (disabled || readOnly) return; if (['LABEL', 'INPUT'].includes((e.target as HTMLElement)?.tagName)) return; const clickEvent = createClickEvent(); localRef.current ?.querySelectorAll('input') .forEach((el) => el?.dispatchEvent(clickEvent)); onClick?.(e); }; const renderreadonlyEffectVariantComponent = () => { switch (readonlyEffectVariant) { case 'info': return ( stopPropagation(e, !disabled && onActionClick)} /> ); case 'clear': return ( stopPropagation(e, !disabled && onClear)} /> ); default: return ''; } }; const renderreadonlyEffectStateComponent = () => { switch (readonlyEffectState) { case 'loading': return ; case 'dragging': return ( ); default: return ''; } }; const renderLabels = () => { return (
{label} {labelSlot && labelSlot}
{subLabel && (
{subLabel}
)}
); }; const renderContent = () => { if (readOnly) { return ( <> {renderreadonlyEffectVariantComponent()}
{renderLabels()}
{renderreadonlyEffectStateComponent()} ); } return ( <> {leadingSlot && (
{leadingSlot}
)} {!isNormal && !selectorReverse && SelectorSlot}
{isNormal && !selectorReverse && SelectorSlot} {LeadingIcon && ( )} {renderLabels()} {TrailingIcon && ( )} {isNormal && selectorReverse && SelectorSlot}
{!isNormal && selectorReverse && SelectorSlot} {(actionLabel || action || actionSlot) && (
{actionSlot && actionSlot} stopPropagation(e, disabled && onActionClick)} />
)} {trailingSlot && (
{trailingSlot}
)} ); }; return ( , setNodeRef as React.ForwardedRef, localRef )} as={as} surface="wrapper" isFontSizeProvider className={cn(gridChildClassname, config[`rt-item-${size}`], 'rt-item', { 'rt-item_squeeze': squeeze, 'rt-item_icon-start': variant === 'space', })} modifier={squeeze ? 'filling' : undefined} surfaceState={getSurfaceState( disabled, readonlyEffectState === 'loading', squeeze )} style={{ ...style, ...(readOnly ? draggingStyle : {}), cursor: cursorComputed, }} aria-labelledby={labelId} onClick={handleClick} >
{renderContent()}
); }; /** * Универсальный компонент для отображения единицы контента. * Может использоваться как самостоятельный (одиночный) компонент, так и в составе RtList. * * {@link RtItemProps} */ export const RtItem = forwardRef(RtInternalItem); RtItem.displayName = 'RtItem';