signal-desktop/ts/components/fun/FunSkinTones.tsx

102 lines
3.1 KiB
TypeScript
Raw Normal View History

// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useCallback, useMemo } from 'react';
import type { Selection } from 'react-aria-components';
import { ListBox, ListBoxItem } from 'react-aria-components';
2025-03-26 12:35:32 -07:00
import type { EmojiParentKey } from './data/emojis';
import {
EmojiSkinTone,
getEmojiVariantByParentKeyAndSkinTone,
2025-03-26 12:35:32 -07:00
} from './data/emojis';
import { strictAssert } from '../../util/assert';
import { FunStaticEmoji } from './FunEmoji';
import type { LocalizerType } from '../../types/I18N';
2025-03-26 12:35:32 -07:00
export type FunSkinTonesListProps = Readonly<{
i18n: LocalizerType;
emoji: EmojiParentKey;
2025-04-07 12:47:38 -07:00
skinTone: EmojiSkinTone | null;
onSelectSkinTone: (skinTone: EmojiSkinTone) => void;
}>;
2025-03-26 12:35:32 -07:00
export function FunSkinTonesList(props: FunSkinTonesListProps): JSX.Element {
const { i18n, onSelectSkinTone } = props;
const handleSelectionChange = useCallback(
(keys: Selection) => {
strictAssert(keys !== 'all', 'Expected single selection');
strictAssert(keys.size === 1, 'Expected single selection');
const [first] = keys.values();
onSelectSkinTone(first as EmojiSkinTone);
},
[onSelectSkinTone]
);
return (
<ListBox
2025-03-26 12:35:32 -07:00
aria-label={i18n('icu:FunSkinTones__List')}
className="FunSkinTones__ListBox"
orientation="horizontal"
2025-04-07 12:47:38 -07:00
selectedKeys={props.skinTone != null ? [props.skinTone] : []}
selectionMode="single"
2025-03-26 12:35:32 -07:00
disallowEmptySelection
onSelectionChange={handleSelectionChange}
>
2025-03-26 12:35:32 -07:00
<FunSkinTonesListItem
emoji={props.emoji}
aria-label={i18n('icu:FunSkinTones__ListItem--Light')}
skinTone={EmojiSkinTone.None}
/>
<FunSkinTonesListItem
emoji={props.emoji}
skinTone={EmojiSkinTone.Type1}
2025-03-26 12:35:32 -07:00
aria-label={i18n('icu:FunSkinTones__ListItem--Light')}
/>
2025-03-26 12:35:32 -07:00
<FunSkinTonesListItem
emoji={props.emoji}
skinTone={EmojiSkinTone.Type2}
2025-03-26 12:35:32 -07:00
aria-label={i18n('icu:FunSkinTones__ListItem--MediumLight')}
/>
2025-03-26 12:35:32 -07:00
<FunSkinTonesListItem
emoji={props.emoji}
skinTone={EmojiSkinTone.Type3}
2025-03-26 12:35:32 -07:00
aria-label={i18n('icu:FunSkinTones__ListItem--Medium')}
/>
2025-03-26 12:35:32 -07:00
<FunSkinTonesListItem
emoji={props.emoji}
skinTone={EmojiSkinTone.Type4}
2025-03-26 12:35:32 -07:00
aria-label={i18n('icu:FunSkinTones__ListItem--MediumDark')}
/>
2025-03-26 12:35:32 -07:00
<FunSkinTonesListItem
emoji={props.emoji}
skinTone={EmojiSkinTone.Type5}
2025-03-26 12:35:32 -07:00
aria-label={i18n('icu:FunSkinTones__ListItem--Dark')}
/>
</ListBox>
);
}
2025-03-26 12:35:32 -07:00
type FunSkinTonesListItemProps = Readonly<{
emoji: EmojiParentKey;
2025-03-26 12:35:32 -07:00
'aria-label': string;
skinTone: EmojiSkinTone;
}>;
2025-03-26 12:35:32 -07:00
function FunSkinTonesListItem(props: FunSkinTonesListItemProps) {
const variant = useMemo(() => {
return getEmojiVariantByParentKeyAndSkinTone(props.emoji, props.skinTone);
}, [props.emoji, props.skinTone]);
return (
2025-03-26 12:35:32 -07:00
<ListBoxItem
id={props.skinTone}
className="FunSkinTones__ListBoxItem"
aria-label={props['aria-label']}
>
<div className="FunSkinTones__ListBoxItemButton">
<FunStaticEmoji role="presentation" size={32} emoji={variant} />
</div>
</ListBoxItem>
);
}