Signal-Desktop/ts/components/fun/FunEmoji.stories.tsx

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>
);
}