mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-09-13 22:32:26 +03:00
extract component from emoji/sticker item and sidebar buttons
This commit is contained in:
parent
f674482911
commit
7ff18a3ed3
6 changed files with 262 additions and 232 deletions
|
@ -1,5 +1,5 @@
|
|||
import { style } from '@vanilla-extract/css';
|
||||
import { DefaultReset, FocusOutline, color, config, toRem } from 'folds';
|
||||
import { DefaultReset, color, config, toRem } from 'folds';
|
||||
|
||||
export const Base = style({
|
||||
maxWidth: toRem(432),
|
||||
|
@ -46,46 +46,3 @@ export const EmojiGroupContent = style([
|
|||
padding: `0 ${config.space.S200}`,
|
||||
},
|
||||
]);
|
||||
|
||||
export const EmojiItem = style([
|
||||
DefaultReset,
|
||||
FocusOutline,
|
||||
{
|
||||
width: toRem(48),
|
||||
height: toRem(48),
|
||||
fontSize: toRem(32),
|
||||
lineHeight: toRem(32),
|
||||
borderRadius: config.radii.R400,
|
||||
cursor: 'pointer',
|
||||
|
||||
':hover': {
|
||||
backgroundColor: color.Surface.ContainerHover,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const StickerItem = style([
|
||||
EmojiItem,
|
||||
{
|
||||
width: toRem(112),
|
||||
height: toRem(112),
|
||||
},
|
||||
]);
|
||||
|
||||
export const CustomEmojiImg = style([
|
||||
DefaultReset,
|
||||
{
|
||||
width: toRem(32),
|
||||
height: toRem(32),
|
||||
objectFit: 'contain',
|
||||
},
|
||||
]);
|
||||
|
||||
export const StickerImg = style([
|
||||
DefaultReset,
|
||||
{
|
||||
width: toRem(96),
|
||||
height: toRem(96),
|
||||
objectFit: 'contain',
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, {
|
|||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { Box, Icon, Icons, Line, Scroll, Text, as, toRem } from 'folds';
|
||||
import { Box, Icons, Line, Scroll, Text, as } from 'folds';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { isKeyHotkey } from 'is-hotkey';
|
||||
import classNames from 'classnames';
|
||||
|
@ -39,12 +39,16 @@ import {
|
|||
EmojiBoardTabs,
|
||||
SidebarStack,
|
||||
SidebarDivider,
|
||||
SidebarBtn,
|
||||
Sidebar,
|
||||
NoStickerPacks,
|
||||
createPreviewDataAtom,
|
||||
Preview,
|
||||
PreviewData,
|
||||
EmojiItem,
|
||||
StickerItem,
|
||||
CustomEmojiItem,
|
||||
ImageGroupIcon,
|
||||
GroupIcon,
|
||||
} from './components';
|
||||
import { EmojiBoardTab, EmojiItemInfo, EmojiType } from './types';
|
||||
|
||||
|
@ -125,81 +129,18 @@ export const EmojiGroup = as<
|
|||
</Box>
|
||||
));
|
||||
|
||||
export function EmojiItem({
|
||||
label,
|
||||
type,
|
||||
data,
|
||||
shortcode,
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
type: EmojiType;
|
||||
data: string;
|
||||
shortcode: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Box
|
||||
as="button"
|
||||
className={css.EmojiItem}
|
||||
type="button"
|
||||
alignItems="Center"
|
||||
justifyContent="Center"
|
||||
title={label}
|
||||
aria-label={`${label} emoji`}
|
||||
data-emoji-type={type}
|
||||
data-emoji-data={data}
|
||||
data-emoji-shortcode={shortcode}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function StickerItem({
|
||||
label,
|
||||
type,
|
||||
data,
|
||||
shortcode,
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
type: EmojiType;
|
||||
data: string;
|
||||
shortcode: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Box
|
||||
as="button"
|
||||
className={css.StickerItem}
|
||||
type="button"
|
||||
alignItems="Center"
|
||||
justifyContent="Center"
|
||||
title={label}
|
||||
aria-label={`${label} sticker`}
|
||||
data-emoji-type={type}
|
||||
data-emoji-data={data}
|
||||
data-emoji-shortcode={shortcode}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function RecentEmojiSidebarStack({ onItemClick }: { onItemClick: (id: string) => void }) {
|
||||
const activeGroupId = useAtomValue(activeGroupIdAtom);
|
||||
|
||||
return (
|
||||
<SidebarStack>
|
||||
<SidebarBtn
|
||||
<GroupIcon
|
||||
active={activeGroupId === RECENT_GROUP_ID}
|
||||
id={RECENT_GROUP_ID}
|
||||
label="Recent"
|
||||
onItemClick={() => onItemClick(RECENT_GROUP_ID)}
|
||||
>
|
||||
<Icon src={Icons.RecentClock} filled={activeGroupId === RECENT_GROUP_ID} />
|
||||
</SidebarBtn>
|
||||
icon={Icons.RecentClock}
|
||||
onClick={onItemClick}
|
||||
/>
|
||||
</SidebarStack>
|
||||
);
|
||||
}
|
||||
|
@ -224,27 +165,19 @@ function ImagePackSidebarStack({
|
|||
{packs.map((pack) => {
|
||||
let label = pack.meta.name;
|
||||
if (!label) label = isUserId(pack.id) ? 'Personal Pack' : mx.getRoom(pack.id)?.name;
|
||||
|
||||
const url =
|
||||
mxcUrlToHttp(mx, pack.getAvatarUrl(usage) ?? '', useAuthentication) || pack.meta.avatar;
|
||||
|
||||
return (
|
||||
<SidebarBtn
|
||||
active={activeGroupId === pack.id}
|
||||
<ImageGroupIcon
|
||||
key={pack.id}
|
||||
active={activeGroupId === pack.id}
|
||||
id={pack.id}
|
||||
label={label || 'Unknown Pack'}
|
||||
onItemClick={onItemClick}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: toRem(24),
|
||||
height: toRem(24),
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
src={
|
||||
mxcUrlToHttp(mx, pack.getAvatarUrl(usage) ?? '', useAuthentication) ||
|
||||
pack.meta.avatar
|
||||
}
|
||||
alt={label || 'Unknown Pack'}
|
||||
label={label ?? 'Unknown Pack'}
|
||||
url={url}
|
||||
onClick={onItemClick}
|
||||
/>
|
||||
</SidebarBtn>
|
||||
);
|
||||
})}
|
||||
</SidebarStack>
|
||||
|
@ -267,15 +200,14 @@ function NativeEmojiSidebarStack({
|
|||
<SidebarStack className={css.NativeEmojiSidebarStack}>
|
||||
<SidebarDivider />
|
||||
{groups.map((group) => (
|
||||
<SidebarBtn
|
||||
<GroupIcon
|
||||
key={group.id}
|
||||
active={activeGroupId === group.id}
|
||||
id={group.id}
|
||||
label={labels[group.id]}
|
||||
onItemClick={onItemClick}
|
||||
>
|
||||
<Icon src={icons[group.id]} filled={activeGroupId === group.id} />
|
||||
</SidebarBtn>
|
||||
icon={icons[group.id]}
|
||||
onClick={onItemClick}
|
||||
/>
|
||||
))}
|
||||
</SidebarStack>
|
||||
);
|
||||
|
@ -293,15 +225,7 @@ export function RecentEmojiGroup({
|
|||
return (
|
||||
<EmojiGroup key={id} id={id} label={label}>
|
||||
{recentEmojis.map((emoji) => (
|
||||
<EmojiItem
|
||||
key={emoji.unicode}
|
||||
label={emoji.label}
|
||||
type={EmojiType.Emoji}
|
||||
data={emoji.unicode}
|
||||
shortcode={emoji.shortcode}
|
||||
>
|
||||
{emoji.unicode}
|
||||
</EmojiItem>
|
||||
<EmojiItem key={emoji.shortcode} emoji={emoji} />
|
||||
))}
|
||||
</EmojiGroup>
|
||||
);
|
||||
|
@ -324,53 +248,29 @@ export function SearchEmojiGroup({
|
|||
}) {
|
||||
return (
|
||||
<EmojiGroup key={id} id={id} label={label}>
|
||||
{tab === EmojiBoardTab.Emoji
|
||||
? searchResult.map((emoji) =>
|
||||
'unicode' in emoji ? (
|
||||
<EmojiItem
|
||||
key={emoji.unicode}
|
||||
label={emoji.label}
|
||||
type={EmojiType.Emoji}
|
||||
data={emoji.unicode}
|
||||
shortcode={emoji.shortcode}
|
||||
>
|
||||
{emoji.unicode}
|
||||
</EmojiItem>
|
||||
) : (
|
||||
<EmojiItem
|
||||
key={emoji.shortcode}
|
||||
label={emoji.body || emoji.shortcode}
|
||||
type={EmojiType.CustomEmoji}
|
||||
data={emoji.url}
|
||||
shortcode={emoji.shortcode}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
className={css.CustomEmojiImg}
|
||||
alt={emoji.body || emoji.shortcode}
|
||||
src={mxcUrlToHttp(mx, emoji.url, useAuthentication) ?? emoji.url}
|
||||
/>
|
||||
</EmojiItem>
|
||||
)
|
||||
)
|
||||
: searchResult.map((emoji) =>
|
||||
'unicode' in emoji ? null : (
|
||||
{searchResult.map((emoji) => {
|
||||
if ('unicode' in emoji) {
|
||||
return <EmojiItem key={emoji.unicode} emoji={emoji} />;
|
||||
}
|
||||
if (tab === EmojiBoardTab.Sticker) {
|
||||
return (
|
||||
<StickerItem
|
||||
key={emoji.shortcode}
|
||||
label={emoji.body || emoji.shortcode}
|
||||
type={EmojiType.Sticker}
|
||||
data={emoji.url}
|
||||
shortcode={emoji.shortcode}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
className={css.StickerImg}
|
||||
alt={emoji.body || emoji.shortcode}
|
||||
src={mxcUrlToHttp(mx, emoji.url, useAuthentication) ?? emoji.url}
|
||||
mx={mx}
|
||||
useAuthentication={useAuthentication}
|
||||
image={emoji}
|
||||
/>
|
||||
</StickerItem>
|
||||
)
|
||||
)}
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CustomEmojiItem
|
||||
key={emoji.shortcode}
|
||||
mx={mx}
|
||||
useAuthentication={useAuthentication}
|
||||
image={emoji}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</EmojiGroup>
|
||||
);
|
||||
}
|
||||
|
@ -392,20 +292,12 @@ export const CustomEmojiGroups = memo(
|
|||
.getImages(ImageUsage.Emoticon)
|
||||
.sort((a, b) => a.shortcode.localeCompare(b.shortcode))
|
||||
.map((image) => (
|
||||
<EmojiItem
|
||||
<CustomEmojiItem
|
||||
key={image.shortcode}
|
||||
label={image.body || image.shortcode}
|
||||
type={EmojiType.CustomEmoji}
|
||||
data={image.url}
|
||||
shortcode={image.shortcode}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
className={css.CustomEmojiImg}
|
||||
alt={image.body || image.shortcode}
|
||||
src={mxcUrlToHttp(mx, image.url, useAuthentication) ?? image.url}
|
||||
mx={mx}
|
||||
useAuthentication={useAuthentication}
|
||||
image={image}
|
||||
/>
|
||||
</EmojiItem>
|
||||
))}
|
||||
</EmojiGroup>
|
||||
))}
|
||||
|
@ -434,18 +326,10 @@ export const StickerGroups = memo(
|
|||
.map((image) => (
|
||||
<StickerItem
|
||||
key={image.shortcode}
|
||||
label={image.body || image.shortcode}
|
||||
type={EmojiType.Sticker}
|
||||
data={image.url}
|
||||
shortcode={image.shortcode}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
className={css.StickerImg}
|
||||
alt={image.body || image.shortcode}
|
||||
src={mxcUrlToHttp(mx, image.url, useAuthentication) ?? image.url}
|
||||
mx={mx}
|
||||
useAuthentication={useAuthentication}
|
||||
image={image}
|
||||
/>
|
||||
</StickerItem>
|
||||
))}
|
||||
</EmojiGroup>
|
||||
))
|
||||
|
@ -458,15 +342,7 @@ export const NativeEmojiGroups = memo(
|
|||
{groups.map((emojiGroup) => (
|
||||
<EmojiGroup key={emojiGroup.id} id={emojiGroup.id} label={labels[emojiGroup.id]}>
|
||||
{emojiGroup.emojis.map((emoji) => (
|
||||
<EmojiItem
|
||||
key={emoji.unicode}
|
||||
label={emoji.label}
|
||||
type={EmojiType.Emoji}
|
||||
data={emoji.unicode}
|
||||
shortcode={emoji.shortcode}
|
||||
>
|
||||
{emoji.unicode}
|
||||
</EmojiItem>
|
||||
<EmojiItem key={emoji.unicode} emoji={emoji} />
|
||||
))}
|
||||
</EmojiGroup>
|
||||
))}
|
||||
|
|
89
src/app/components/emoji-board/components/Item.tsx
Normal file
89
src/app/components/emoji-board/components/Item.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
import React from 'react';
|
||||
import { Box } from 'folds';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import { EmojiType } from '../types';
|
||||
import * as css from './styles.css';
|
||||
import { PackImageReader } from '../../../plugins/custom-emoji';
|
||||
import { IEmoji } from '../../../plugins/emoji';
|
||||
import { mxcUrlToHttp } from '../../../utils/matrix';
|
||||
|
||||
type EmojiItemProps = {
|
||||
emoji: IEmoji;
|
||||
};
|
||||
export function EmojiItem({ emoji }: EmojiItemProps) {
|
||||
return (
|
||||
<Box
|
||||
as="button"
|
||||
type="button"
|
||||
alignItems="Center"
|
||||
justifyContent="Center"
|
||||
className={css.EmojiItem}
|
||||
title={emoji.label}
|
||||
aria-label={`${emoji.label} emoji`}
|
||||
data-emoji-type={EmojiType.Emoji}
|
||||
data-emoji-data={emoji.unicode}
|
||||
data-emoji-shortcode={emoji.shortcode}
|
||||
>
|
||||
{emoji.unicode}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
type CustomEmojiItemProps = {
|
||||
mx: MatrixClient;
|
||||
useAuthentication?: boolean;
|
||||
image: PackImageReader;
|
||||
};
|
||||
export function CustomEmojiItem({ mx, useAuthentication, image }: CustomEmojiItemProps) {
|
||||
return (
|
||||
<Box
|
||||
as="button"
|
||||
type="button"
|
||||
alignItems="Center"
|
||||
justifyContent="Center"
|
||||
className={css.EmojiItem}
|
||||
title={image.body || image.shortcode}
|
||||
aria-label={`${image.body || image.shortcode} emoji`}
|
||||
data-emoji-type={EmojiType.CustomEmoji}
|
||||
data-emoji-data={image.url}
|
||||
data-emoji-shortcode={image.shortcode}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
className={css.CustomEmojiImg}
|
||||
alt={image.body || image.shortcode}
|
||||
src={mxcUrlToHttp(mx, image.url, useAuthentication) ?? image.url}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
type StickerItemProps = {
|
||||
mx: MatrixClient;
|
||||
useAuthentication?: boolean;
|
||||
image: PackImageReader;
|
||||
};
|
||||
|
||||
export function StickerItem({ mx, useAuthentication, image }: StickerItemProps) {
|
||||
return (
|
||||
<Box
|
||||
as="button"
|
||||
type="button"
|
||||
alignItems="Center"
|
||||
justifyContent="Center"
|
||||
className={css.StickerItem}
|
||||
title={image.body || image.shortcode}
|
||||
aria-label={`${image.body || image.shortcode} emoji`}
|
||||
data-emoji-type={EmojiType.Sticker}
|
||||
data-emoji-data={image.url}
|
||||
data-emoji-shortcode={image.shortcode}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
className={css.StickerImg}
|
||||
alt={image.body || image.shortcode}
|
||||
src={mxcUrlToHttp(mx, image.url, useAuthentication) ?? image.url}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
|
@ -1,5 +1,17 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { Box, Scroll, Line, as, TooltipProvider, Tooltip, Text, IconButton } from 'folds';
|
||||
import {
|
||||
Box,
|
||||
Scroll,
|
||||
Line,
|
||||
as,
|
||||
TooltipProvider,
|
||||
Tooltip,
|
||||
Text,
|
||||
IconButton,
|
||||
Icon,
|
||||
IconSrc,
|
||||
Icons,
|
||||
} from 'folds';
|
||||
import classNames from 'classnames';
|
||||
import * as css from './styles.css';
|
||||
|
||||
|
@ -31,17 +43,17 @@ export function SidebarDivider() {
|
|||
return <Line className={css.SidebarDivider} size="300" variant="Surface" />;
|
||||
}
|
||||
|
||||
export function SidebarBtn<T extends string>({
|
||||
function SidebarBtn<T extends string>({
|
||||
active,
|
||||
label,
|
||||
id,
|
||||
onItemClick,
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
active?: boolean;
|
||||
label: string;
|
||||
id: T;
|
||||
onItemClick: (id: T) => void;
|
||||
onClick: (id: T) => void;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
|
@ -59,7 +71,7 @@ export function SidebarBtn<T extends string>({
|
|||
aria-pressed={active}
|
||||
aria-labelledby={`SidebarStackItem-${id}-label`}
|
||||
ref={ref}
|
||||
onClick={() => onItemClick(id)}
|
||||
onClick={() => onClick(id)}
|
||||
size="400"
|
||||
radii="300"
|
||||
variant="Surface"
|
||||
|
@ -70,3 +82,49 @@ export function SidebarBtn<T extends string>({
|
|||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
type GroupIconProps<T extends string> = {
|
||||
active: boolean;
|
||||
id: T;
|
||||
label: string;
|
||||
icon: IconSrc;
|
||||
onClick: (id: T) => void;
|
||||
};
|
||||
export function GroupIcon<T extends string>({
|
||||
active,
|
||||
id,
|
||||
label,
|
||||
icon,
|
||||
onClick,
|
||||
}: GroupIconProps<T>) {
|
||||
return (
|
||||
<SidebarBtn active={active} id={id} label={label} onClick={onClick}>
|
||||
<Icon src={icon} filled={active} />
|
||||
</SidebarBtn>
|
||||
);
|
||||
}
|
||||
|
||||
type ImageGroupIconProps<T extends string> = {
|
||||
active: boolean;
|
||||
id: T;
|
||||
label: string;
|
||||
url?: string;
|
||||
onClick: (id: T) => void;
|
||||
};
|
||||
export function ImageGroupIcon<T extends string>({
|
||||
active,
|
||||
id,
|
||||
label,
|
||||
url,
|
||||
onClick,
|
||||
}: ImageGroupIconProps<T>) {
|
||||
return (
|
||||
<SidebarBtn active={active} id={id} label={label} onClick={onClick}>
|
||||
{url ? (
|
||||
<Icon src={Icons.Photo} filled={active} />
|
||||
) : (
|
||||
<img className={css.SidebarBtnImg} src={url} alt={label} />
|
||||
)}
|
||||
</SidebarBtn>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,3 +3,4 @@ export * from './Tabs';
|
|||
export * from './Sidebar';
|
||||
export * from './NoStickerPacks';
|
||||
export * from './Preview';
|
||||
export * from './Item';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { style } from '@vanilla-extract/css';
|
||||
import { toRem, color, config, DefaultReset } from 'folds';
|
||||
import { toRem, color, config, DefaultReset, FocusOutline } from 'folds';
|
||||
|
||||
export const Sidebar = style({
|
||||
width: toRem(54),
|
||||
|
@ -21,6 +21,12 @@ export const SidebarDivider = style({
|
|||
width: toRem(18),
|
||||
});
|
||||
|
||||
export const SidebarBtnImg = style({
|
||||
width: toRem(24),
|
||||
height: toRem(24),
|
||||
objectFit: 'contain',
|
||||
});
|
||||
|
||||
export const Preview = style({
|
||||
padding: config.space.S200,
|
||||
margin: config.space.S300,
|
||||
|
@ -49,3 +55,46 @@ export const PreviewImg = style([
|
|||
objectFit: 'contain',
|
||||
},
|
||||
]);
|
||||
|
||||
export const EmojiItem = style([
|
||||
DefaultReset,
|
||||
FocusOutline,
|
||||
{
|
||||
width: toRem(48),
|
||||
height: toRem(48),
|
||||
fontSize: toRem(32),
|
||||
lineHeight: toRem(32),
|
||||
borderRadius: config.radii.R400,
|
||||
cursor: 'pointer',
|
||||
|
||||
':hover': {
|
||||
backgroundColor: color.Surface.ContainerHover,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const StickerItem = style([
|
||||
EmojiItem,
|
||||
{
|
||||
width: toRem(112),
|
||||
height: toRem(112),
|
||||
},
|
||||
]);
|
||||
|
||||
export const CustomEmojiImg = style([
|
||||
DefaultReset,
|
||||
{
|
||||
width: toRem(32),
|
||||
height: toRem(32),
|
||||
objectFit: 'contain',
|
||||
},
|
||||
]);
|
||||
|
||||
export const StickerImg = style([
|
||||
DefaultReset,
|
||||
{
|
||||
width: toRem(96),
|
||||
height: toRem(96),
|
||||
objectFit: 'contain',
|
||||
},
|
||||
]);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue