extract layout and emoji group components

This commit is contained in:
Ajay Bura 2025-09-14 08:28:54 +05:30
parent cd963d91d3
commit 90d9d4243e
7 changed files with 159 additions and 129 deletions

View file

@ -1,48 +0,0 @@
import { style } from '@vanilla-extract/css';
import { DefaultReset, color, config, toRem } from 'folds';
export const Base = style({
maxWidth: toRem(432),
width: `calc(100vw - 2 * ${config.space.S400})`,
height: toRem(450),
backgroundColor: color.Surface.Container,
color: color.Surface.OnContainer,
border: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
borderRadius: config.radii.R400,
boxShadow: config.shadow.E200,
overflow: 'hidden',
});
export const NativeEmojiSidebarStack = style({
position: 'sticky',
bottom: '-67%',
zIndex: 1,
});
export const Header = style({
padding: config.space.S300,
paddingBottom: 0,
});
export const EmojiGroup = style({
padding: `${config.space.S300} 0`,
});
export const EmojiGroupLabel = style({
position: 'sticky',
top: config.space.S200,
zIndex: 1,
margin: 'auto',
padding: `${config.space.S100} ${config.space.S200}`,
borderRadius: config.radii.Pill,
backgroundColor: color.SurfaceVariant.Container,
color: color.SurfaceVariant.OnContainer,
});
export const EmojiGroupContent = style([
DefaultReset,
{
padding: `0 ${config.space.S200}`,
},
]);

View file

@ -3,20 +3,17 @@ import React, {
FocusEventHandler,
MouseEventHandler,
UIEventHandler,
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
} from 'react';
import { Box, Icons, Line, Scroll, Text, as } from 'folds';
import { Box, Icons, Scroll } from 'folds';
import FocusTrap from 'focus-trap-react';
import { isKeyHotkey } from 'is-hotkey';
import classNames from 'classnames';
import { MatrixClient, Room } from 'matrix-js-sdk';
import { Atom, atom, useAtomValue, useSetAtom } from 'jotai';
import * as css from './EmojiBoard.css';
import { IEmoji, IEmojiGroup, emojiGroups, emojis } from '../../plugins/emoji';
import { IEmojiGroupLabels, useEmojiGroupLabels } from './useEmojiGroupLabels';
import { IEmojiGroupIcons, useEmojiGroupIcons } from './useEmojiGroupIcons';
@ -48,86 +45,16 @@ import {
CustomEmojiItem,
ImageGroupIcon,
GroupIcon,
getEmojiItemInfo,
getDOMGroupId,
EmojiGroup,
EmojiBoardLayout,
} from './components';
import { EmojiBoardTab, EmojiItemInfo, EmojiType } from './types';
import { EmojiBoardTab, EmojiType } from './types';
const RECENT_GROUP_ID = 'recent_group';
const SEARCH_GROUP_ID = 'search_group';
const getDOMGroupId = (id: string): string => `EmojiBoardGroup-${id}`;
const getEmojiItemInfo = (element: Element): EmojiItemInfo | undefined => {
const type = element.getAttribute('data-emoji-type') as EmojiType | undefined;
const data = element.getAttribute('data-emoji-data');
const label = element.getAttribute('title');
const shortcode = element.getAttribute('data-emoji-shortcode');
if (type && data && shortcode && label)
return {
type,
data,
shortcode,
label,
};
return undefined;
};
const activeGroupIdAtom = atom<string | undefined>(undefined);
const EmojiBoardLayout = as<
'div',
{
header: ReactNode;
sidebar?: ReactNode;
children: ReactNode;
}
>(({ className, header, sidebar, children, ...props }, ref) => (
<Box
display="InlineFlex"
className={classNames(css.Base, className)}
direction="Row"
{...props}
ref={ref}
>
<Box direction="Column" grow="Yes">
<Box className={css.Header} direction="Column" shrink="No">
{header}
</Box>
{children}
</Box>
<Line size="300" direction="Vertical" />
{sidebar}
</Box>
));
export const EmojiGroup = as<
'div',
{
id: string;
label: string;
children: ReactNode;
}
>(({ className, id, label, children, ...props }, ref) => (
<Box
id={getDOMGroupId(id)}
data-group-id={id}
className={classNames(css.EmojiGroup, className)}
direction="Column"
gap="200"
{...props}
ref={ref}
>
<Text id={`EmojiGroup-${id}-label`} as="label" className={css.EmojiGroupLabel} size="O400">
{label}
</Text>
<div aria-labelledby={`EmojiGroup-${id}-label`} className={css.EmojiGroupContent}>
<Box wrap="Wrap" justifyContent="Center">
{children}
</Box>
</div>
</Box>
));
type EmojiSidebarProps = {
activeGroupAtom: Atom<string | undefined>;
handleOpenGroup: (groupId: string) => void;
@ -185,7 +112,13 @@ function EmojiSidebar({
})}
</SidebarStack>
)}
<SidebarStack className={css.NativeEmojiSidebarStack}>
<SidebarStack
style={{
position: 'sticky',
bottom: '-67%',
zIndex: 1,
}}
>
<SidebarDivider />
{groups.map((group) => (
<GroupIcon
@ -397,7 +330,9 @@ export function EmojiBoard({
[emojiTab]
);
const setPreviewData = useSetAtom(previewAtom);
const activeGroupIdAtom = useMemo(() => atom<string | undefined>(undefined), []);
const setActiveGroupId = useSetAtom(activeGroupIdAtom);
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const emojiGroupLabels = useEmojiGroupLabels();

View file

@ -0,0 +1,34 @@
import { as, Box, Text } from 'folds';
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import * as css from './styles.css';
export const getDOMGroupId = (id: string): string => `EmojiBoardGroup-${id}`;
export const EmojiGroup = as<
'div',
{
id: string;
label: string;
children: ReactNode;
}
>(({ className, id, label, children, ...props }, ref) => (
<Box
id={getDOMGroupId(id)}
data-group-id={id}
className={classNames(css.EmojiGroup, className)}
direction="Column"
gap="200"
{...props}
ref={ref}
>
<Text id={`EmojiGroup-${id}-label`} as="label" className={css.EmojiGroupLabel} size="O400">
{label}
</Text>
<div aria-labelledby={`EmojiGroup-${id}-label`} className={css.EmojiGroupContent}>
<Box wrap="Wrap" justifyContent="Center">
{children}
</Box>
</div>
</Box>
));

View file

@ -1,12 +1,28 @@
import React from 'react';
import { Box } from 'folds';
import { MatrixClient } from 'matrix-js-sdk';
import { EmojiType } from '../types';
import { EmojiItemInfo, 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';
export const getEmojiItemInfo = (element: Element): EmojiItemInfo | undefined => {
const label = element.getAttribute('title');
const type = element.getAttribute('data-emoji-type') as EmojiType | undefined;
const data = element.getAttribute('data-emoji-data');
const shortcode = element.getAttribute('data-emoji-shortcode');
if (type && data && shortcode && label)
return {
type,
data,
shortcode,
label,
};
return undefined;
};
type EmojiItemProps = {
emoji: IEmoji;
};

View file

@ -0,0 +1,30 @@
import { as, Box, Line } from 'folds';
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import * as css from './styles.css';
export const EmojiBoardLayout = as<
'div',
{
header: ReactNode;
sidebar?: ReactNode;
children: ReactNode;
}
>(({ className, header, sidebar, children, ...props }, ref) => (
<Box
display="InlineFlex"
className={classNames(css.Base, className)}
direction="Row"
{...props}
ref={ref}
>
<Box direction="Column" grow="Yes">
<Box className={css.Header} direction="Column" shrink="No">
{header}
</Box>
{children}
</Box>
<Line size="300" direction="Vertical" />
{sidebar}
</Box>
));

View file

@ -4,3 +4,5 @@ export * from './Sidebar';
export * from './NoStickerPacks';
export * from './Preview';
export * from './Item';
export * from './Group';
export * from './Layout';

View file

@ -1,6 +1,31 @@
import { style } from '@vanilla-extract/css';
import { toRem, color, config, DefaultReset, FocusOutline } from 'folds';
/**
* Layout
*/
export const Base = style({
maxWidth: toRem(432),
width: `calc(100vw - 2 * ${config.space.S400})`,
height: toRem(450),
backgroundColor: color.Surface.Container,
color: color.Surface.OnContainer,
border: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
borderRadius: config.radii.R400,
boxShadow: config.shadow.E200,
overflow: 'hidden',
});
export const Header = style({
padding: config.space.S300,
paddingBottom: 0,
});
/**
* Sidebar
*/
export const Sidebar = style({
width: toRem(54),
backgroundColor: color.Surface.Container,
@ -27,6 +52,10 @@ export const SidebarBtnImg = style({
objectFit: 'contain',
});
/**
* Preview
*/
export const Preview = style({
padding: config.space.S200,
margin: config.space.S300,
@ -56,6 +85,38 @@ export const PreviewImg = style([
},
]);
/**
* Group
*/
export const EmojiGroup = style({
position: 'relative',
padding: `${config.space.S300} 0`,
});
export const EmojiGroupLabel = style({
position: 'sticky',
top: config.space.S200,
zIndex: 1,
margin: 'auto',
padding: `${config.space.S100} ${config.space.S200}`,
borderRadius: config.radii.Pill,
backgroundColor: color.SurfaceVariant.Container,
color: color.SurfaceVariant.OnContainer,
});
export const EmojiGroupContent = style([
DefaultReset,
{
padding: `0 ${config.space.S200}`,
},
]);
/**
* Item
*/
export const EmojiItem = style([
DefaultReset,
FocusOutline,