120 lines
3.2 KiB
TypeScript
120 lines
3.2 KiB
TypeScript
// Copyright 2025 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
import { chunk } from 'lodash';
|
|
import React, { useCallback, useEffect, useRef } from 'react';
|
|
import { type ComponentMeta } from '../../storybook/types';
|
|
import type { FunStaticEmojiProps } from './FunEmoji';
|
|
import { FunStaticEmoji } from './FunEmoji';
|
|
import {
|
|
_allEmojiVariantKeys,
|
|
getEmojiParentByKey,
|
|
getEmojiParentKeyByVariantKey,
|
|
getEmojiVariantByKey,
|
|
} from './data/emojis';
|
|
|
|
export default {
|
|
title: 'Components/Fun/FunEmoji',
|
|
component: All,
|
|
args: {
|
|
size: 16,
|
|
},
|
|
argTypes: {
|
|
size: { control: { type: 'select' }, options: [16, 32] },
|
|
},
|
|
} satisfies ComponentMeta<AllProps>;
|
|
|
|
const COLUMNS = 8;
|
|
|
|
type AllProps = Pick<FunStaticEmojiProps, 'size'>;
|
|
|
|
export function All(props: AllProps): JSX.Element {
|
|
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
const data = Array.from(_allEmojiVariantKeys());
|
|
const rows = chunk(data, COLUMNS);
|
|
|
|
const getScrollElement = useCallback(() => {
|
|
return scrollerRef.current;
|
|
}, []);
|
|
|
|
const estimateSize = useCallback(() => {
|
|
return props.size;
|
|
}, [props.size]);
|
|
|
|
const rowVirtualizer = useVirtualizer({
|
|
count: rows.length,
|
|
getScrollElement,
|
|
estimateSize,
|
|
gap: 4,
|
|
});
|
|
|
|
const lastMeasuredSizeRef = useRef(props.size);
|
|
useEffect(() => {
|
|
if (lastMeasuredSizeRef.current !== props.size) {
|
|
rowVirtualizer.measure();
|
|
lastMeasuredSizeRef.current = props.size;
|
|
}
|
|
}, [rowVirtualizer, props.size]);
|
|
|
|
return (
|
|
<div
|
|
ref={scrollerRef}
|
|
style={{
|
|
overflow: 'auto',
|
|
height: 400,
|
|
padding: 10,
|
|
border: '1px solid',
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
position: 'relative',
|
|
width: '100%',
|
|
height: rowVirtualizer.getTotalSize(),
|
|
}}
|
|
>
|
|
{rowVirtualizer.getVirtualItems().map(rowItem => {
|
|
const row = rows[rowItem.index];
|
|
return (
|
|
<div
|
|
key={rowItem.index}
|
|
style={{
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
width: '100%',
|
|
height: rowItem.size,
|
|
transform: `translate(0, ${rowItem.start}px)`,
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
gap: 4,
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
{row.map(emojiVariantKey => {
|
|
const variant = getEmojiVariantByKey(emojiVariantKey);
|
|
const parentKey =
|
|
getEmojiParentKeyByVariantKey(emojiVariantKey);
|
|
const parent = getEmojiParentByKey(parentKey);
|
|
return (
|
|
<div
|
|
key={emojiVariantKey}
|
|
style={{ display: 'flex', outline: '1px solid' }}
|
|
>
|
|
<FunStaticEmoji
|
|
role="img"
|
|
aria-label={parent.englishShortNameDefault}
|
|
size={props.size}
|
|
emoji={variant}
|
|
/>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|