'use client'; import * as React from 'react'; import { classNames, hasReactNode } from '@vkontakte/vkjs'; import { useConfigDirection } from '../../hooks/useConfigDirection'; import type { HTMLAttributesWithRootRef } from '../../types'; import { RootComponent } from '../RootComponent/RootComponent'; import { Caption } from '../Typography/Caption/Caption'; import { Footnote } from '../Typography/Footnote/Footnote'; import styles from './UsersStack.module.css'; const stylesSize = { s: styles.sizeS, m: styles.sizeM, l: styles.sizeL, }; const avatarsPositionStyles = { 'inline-start': styles.avatarsPositionInlineStart, 'inline-end': styles.avatarsPositionInlineEnd, 'block-start': styles.avatarsPositionBlockStart, }; export type UsersStackRenderWrapperProps = { /** * Контент для обертки. */ children: React.ReactElement; /** * Путь до фотографии. */ ['data-src']: string; }; export type UsersStackPhoto = { /** * Путь до фотографии. */ src: string; /** * Функция для рендера обертки над фотографией. */ renderWrapper?: (props: UsersStackRenderWrapperProps) => React.ReactElement; }; export interface UsersStackProps extends HTMLAttributesWithRootRef { /** * Массив ссылок на фотографии либо массив структур типа `UsersStackPhoto`. */ photos?: string[] | UsersStackPhoto[]; /** * Размер аватарок. */ size?: 's' | 'm' | 'l'; /** * Количество аватарок, которые будут показаны. * Если в массиве `photos` больше элементов и не используется размер `s`, то будет показано количество остальных элементов. */ visibleCount?: number; /** * Число, которое будет указано в счетчике. * По умолчанию высчитывается по формуле `photos.length - visibleCount`. * Если число больше 99, то счетчик скроется. */ count?: number; /** * Определяет положение аватаров * Режим `block-start` рекомендуется использовать с размером `m`. */ avatarsPosition?: 'inline-start' | 'block-start' | 'inline-end'; } interface PathElementProps extends React.SVGAttributes { /** * Размер фотографии. */ photoSize: number; /** * Тип обрезания фотографии. */ direction: 'circle' | 'right' | 'left'; } type PhotoSizeType = 16 | 24 | 32; function PathElement({ photoSize, direction, ...props }: PathElementProps) { switch (direction) { case 'circle': const radius = photoSize / 2; return ; case 'right': switch (photoSize) { case 16: return ( ); case 24: return ( ); default: return ( ); } default: switch (photoSize) { case 16: return ( ); case 24: return ( ); default: return ( ); } } } const photoSizes: Record, PhotoSizeType> = { s: 16, m: 24, l: 32, }; /** * @see https://vkui.io/components/users-stack */ export const UsersStack = ({ photos = [], visibleCount = 3, count = Math.max(0, photos.length - visibleCount), size = 'm', children, avatarsPosition = 'inline-start', ...restProps }: UsersStackProps): React.ReactNode => { const cmpId = React.useId(); const direction = useConfigDirection(); const canShowOthers = count > 0 && count < 100 && size !== 's'; const CounterTypography = size === 'l' ? Footnote : Caption; const photoSize = photoSizes[size]; const directionClip = direction === 'ltr' ? (canShowOthers ? 'right' : 'left') : canShowOthers ? 'left' : 'right'; const photosElements = photos.slice(0, visibleCount).map((photo, i) => { const direction = i === 0 && !canShowOthers ? 'circle' : directionClip; const id = `UsersStackDefs${cmpId}${i}`; const hrefID = `#${id}`; const maskID = `UsersStackMask${cmpId}${i}`; const isPhotoType = typeof photo === 'object'; const photoSrc = isPhotoType ? photo.src : photo; let photoElement = ( ); if (isPhotoType && photo.renderWrapper) { photoElement = photo.renderWrapper({ 'children': photoElement, 'data-src': photoSrc, }); } return (
{photoElement}
); }); const othersElement = canShowOthers ? (
+{count}
) : null; return ( {(photosElements.length > 0 || othersElement) && (
{photosElements} {othersElement}
)} {hasReactNode(children) && {children}}
); };