mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-05 15:00:30 +03:00
extract layout and emoji group components
This commit is contained in:
parent
cd963d91d3
commit
90d9d4243e
7 changed files with 159 additions and 129 deletions
|
|
@ -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}`,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
@ -3,20 +3,17 @@ import React, {
|
||||||
FocusEventHandler,
|
FocusEventHandler,
|
||||||
MouseEventHandler,
|
MouseEventHandler,
|
||||||
UIEventHandler,
|
UIEventHandler,
|
||||||
ReactNode,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Box, Icons, Line, Scroll, Text, as } from 'folds';
|
import { Box, Icons, Scroll } from 'folds';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import { isKeyHotkey } from 'is-hotkey';
|
import { isKeyHotkey } from 'is-hotkey';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { MatrixClient, Room } from 'matrix-js-sdk';
|
import { MatrixClient, Room } from 'matrix-js-sdk';
|
||||||
import { Atom, atom, useAtomValue, useSetAtom } from 'jotai';
|
import { Atom, atom, useAtomValue, useSetAtom } from 'jotai';
|
||||||
|
|
||||||
import * as css from './EmojiBoard.css';
|
|
||||||
import { IEmoji, IEmojiGroup, emojiGroups, emojis } from '../../plugins/emoji';
|
import { IEmoji, IEmojiGroup, emojiGroups, emojis } from '../../plugins/emoji';
|
||||||
import { IEmojiGroupLabels, useEmojiGroupLabels } from './useEmojiGroupLabels';
|
import { IEmojiGroupLabels, useEmojiGroupLabels } from './useEmojiGroupLabels';
|
||||||
import { IEmojiGroupIcons, useEmojiGroupIcons } from './useEmojiGroupIcons';
|
import { IEmojiGroupIcons, useEmojiGroupIcons } from './useEmojiGroupIcons';
|
||||||
|
|
@ -48,86 +45,16 @@ import {
|
||||||
CustomEmojiItem,
|
CustomEmojiItem,
|
||||||
ImageGroupIcon,
|
ImageGroupIcon,
|
||||||
GroupIcon,
|
GroupIcon,
|
||||||
|
getEmojiItemInfo,
|
||||||
|
getDOMGroupId,
|
||||||
|
EmojiGroup,
|
||||||
|
EmojiBoardLayout,
|
||||||
} from './components';
|
} from './components';
|
||||||
import { EmojiBoardTab, EmojiItemInfo, EmojiType } from './types';
|
import { EmojiBoardTab, EmojiType } from './types';
|
||||||
|
|
||||||
const RECENT_GROUP_ID = 'recent_group';
|
const RECENT_GROUP_ID = 'recent_group';
|
||||||
const SEARCH_GROUP_ID = 'search_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 = {
|
type EmojiSidebarProps = {
|
||||||
activeGroupAtom: Atom<string | undefined>;
|
activeGroupAtom: Atom<string | undefined>;
|
||||||
handleOpenGroup: (groupId: string) => void;
|
handleOpenGroup: (groupId: string) => void;
|
||||||
|
|
@ -185,7 +112,13 @@ function EmojiSidebar({
|
||||||
})}
|
})}
|
||||||
</SidebarStack>
|
</SidebarStack>
|
||||||
)}
|
)}
|
||||||
<SidebarStack className={css.NativeEmojiSidebarStack}>
|
<SidebarStack
|
||||||
|
style={{
|
||||||
|
position: 'sticky',
|
||||||
|
bottom: '-67%',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SidebarDivider />
|
<SidebarDivider />
|
||||||
{groups.map((group) => (
|
{groups.map((group) => (
|
||||||
<GroupIcon
|
<GroupIcon
|
||||||
|
|
@ -397,7 +330,9 @@ export function EmojiBoard({
|
||||||
[emojiTab]
|
[emojiTab]
|
||||||
);
|
);
|
||||||
const setPreviewData = useSetAtom(previewAtom);
|
const setPreviewData = useSetAtom(previewAtom);
|
||||||
|
const activeGroupIdAtom = useMemo(() => atom<string | undefined>(undefined), []);
|
||||||
const setActiveGroupId = useSetAtom(activeGroupIdAtom);
|
const setActiveGroupId = useSetAtom(activeGroupIdAtom);
|
||||||
|
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
const emojiGroupLabels = useEmojiGroupLabels();
|
const emojiGroupLabels = useEmojiGroupLabels();
|
||||||
|
|
|
||||||
34
src/app/components/emoji-board/components/Group.tsx
Normal file
34
src/app/components/emoji-board/components/Group.tsx
Normal 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>
|
||||||
|
));
|
||||||
|
|
@ -1,12 +1,28 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box } from 'folds';
|
import { Box } from 'folds';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import { EmojiType } from '../types';
|
import { EmojiItemInfo, EmojiType } from '../types';
|
||||||
import * as css from './styles.css';
|
import * as css from './styles.css';
|
||||||
import { PackImageReader } from '../../../plugins/custom-emoji';
|
import { PackImageReader } from '../../../plugins/custom-emoji';
|
||||||
import { IEmoji } from '../../../plugins/emoji';
|
import { IEmoji } from '../../../plugins/emoji';
|
||||||
import { mxcUrlToHttp } from '../../../utils/matrix';
|
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 = {
|
type EmojiItemProps = {
|
||||||
emoji: IEmoji;
|
emoji: IEmoji;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
30
src/app/components/emoji-board/components/Layout.tsx
Normal file
30
src/app/components/emoji-board/components/Layout.tsx
Normal 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>
|
||||||
|
));
|
||||||
|
|
@ -4,3 +4,5 @@ export * from './Sidebar';
|
||||||
export * from './NoStickerPacks';
|
export * from './NoStickerPacks';
|
||||||
export * from './Preview';
|
export * from './Preview';
|
||||||
export * from './Item';
|
export * from './Item';
|
||||||
|
export * from './Group';
|
||||||
|
export * from './Layout';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,31 @@
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
import { toRem, color, config, DefaultReset, FocusOutline } from 'folds';
|
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({
|
export const Sidebar = style({
|
||||||
width: toRem(54),
|
width: toRem(54),
|
||||||
backgroundColor: color.Surface.Container,
|
backgroundColor: color.Surface.Container,
|
||||||
|
|
@ -27,6 +52,10 @@ export const SidebarBtnImg = style({
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preview
|
||||||
|
*/
|
||||||
|
|
||||||
export const Preview = style({
|
export const Preview = style({
|
||||||
padding: config.space.S200,
|
padding: config.space.S200,
|
||||||
margin: config.space.S300,
|
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([
|
export const EmojiItem = style([
|
||||||
DefaultReset,
|
DefaultReset,
|
||||||
FocusOutline,
|
FocusOutline,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue