mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-05 06:50:28 +03:00
Add arrow to message bubbles and improve spacing (#2474)
Some checks failed
Deploy to Netlify (dev) / Deploy to Netlify (push) Has been cancelled
Some checks failed
Deploy to Netlify (dev) / Deploy to Netlify (push) Has been cancelled
* Add arrow to message bubbles and improve spacing * make bubble message avatar smaller * add bubble layout for event content * adjust bubble arrow * fix missing return statement for event content * hide bubble for event content * add new arrow to bubble message * fix avatar username relative alignment * fix types * fix code block header background * revert avatar size and make arrow less sharp * show event messages timestamp to right when bubble is hidden * fix avatar base css * move message header outside bubble * fix event time appears on left in hidden bubles
This commit is contained in:
parent
31efbf73b7
commit
afc251aa7c
6 changed files with 111 additions and 27 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
import { Box, Icon, IconSrc } from 'folds';
|
import { Box, Icon, IconSrc } from 'folds';
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { CompactLayout, ModernLayout } from '..';
|
import { BubbleLayout, CompactLayout, ModernLayout } from '..';
|
||||||
import { MessageLayout } from '../../../state/settings';
|
import { MessageLayout } from '../../../state/settings';
|
||||||
|
|
||||||
export type EventContentProps = {
|
export type EventContentProps = {
|
||||||
|
|
@ -30,9 +30,15 @@ export function EventContent({ messageLayout, time, iconSrc, content }: EventCon
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
return messageLayout === MessageLayout.Compact ? (
|
if (messageLayout === MessageLayout.Compact) {
|
||||||
<CompactLayout before={beforeJSX}>{msgContentJSX}</CompactLayout>
|
return <CompactLayout before={beforeJSX}>{msgContentJSX}</CompactLayout>;
|
||||||
) : (
|
}
|
||||||
<ModernLayout before={beforeJSX}>{msgContentJSX}</ModernLayout>
|
if (messageLayout === MessageLayout.Bubble) {
|
||||||
|
return (
|
||||||
|
<BubbleLayout hideBubble before={beforeJSX}>
|
||||||
|
{msgContentJSX}
|
||||||
|
</BubbleLayout>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
return <ModernLayout before={beforeJSX}>{msgContentJSX}</ModernLayout>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,63 @@
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { Box, as } from 'folds';
|
import classNames from 'classnames';
|
||||||
|
import { Box, ContainerColor, as, color } from 'folds';
|
||||||
import * as css from './layout.css';
|
import * as css from './layout.css';
|
||||||
|
|
||||||
|
type BubbleArrowProps = {
|
||||||
|
variant: ContainerColor;
|
||||||
|
};
|
||||||
|
function BubbleLeftArrow({ variant }: BubbleArrowProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={css.BubbleLeftArrow}
|
||||||
|
width="9"
|
||||||
|
height="8"
|
||||||
|
viewBox="0 0 9 8"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M9.00004 8V0H4.82847C3.04666 0 2.15433 2.15428 3.41426 3.41421L8.00004 8H9.00004Z"
|
||||||
|
fill={color[variant].Container}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type BubbleLayoutProps = {
|
type BubbleLayoutProps = {
|
||||||
|
hideBubble?: boolean;
|
||||||
before?: ReactNode;
|
before?: ReactNode;
|
||||||
|
header?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BubbleLayout = as<'div', BubbleLayoutProps>(({ before, children, ...props }, ref) => (
|
export const BubbleLayout = as<'div', BubbleLayoutProps>(
|
||||||
|
({ hideBubble, before, header, children, ...props }, ref) => (
|
||||||
<Box gap="300" {...props} ref={ref}>
|
<Box gap="300" {...props} ref={ref}>
|
||||||
<Box className={css.BubbleBefore} shrink="No">
|
<Box className={css.BubbleBefore} shrink="No">
|
||||||
{before}
|
{before}
|
||||||
</Box>
|
</Box>
|
||||||
<Box className={css.BubbleContent} direction="Column">
|
<Box grow="Yes" direction="Column">
|
||||||
|
{header}
|
||||||
|
{hideBubble ? (
|
||||||
|
children
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
<Box
|
||||||
|
className={
|
||||||
|
hideBubble
|
||||||
|
? undefined
|
||||||
|
: classNames(css.BubbleContent, before ? css.BubbleContentArrowLeft : undefined)
|
||||||
|
}
|
||||||
|
direction="Column"
|
||||||
|
>
|
||||||
|
{before ? <BubbleLeftArrow variant="SurfaceVariant" /> : null}
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
));
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ export const CompactHeader = style([
|
||||||
export const AvatarBase = style({
|
export const AvatarBase = style({
|
||||||
paddingTop: toRem(4),
|
paddingTop: toRem(4),
|
||||||
transition: 'transform 200ms cubic-bezier(0, 0.8, 0.67, 0.97)',
|
transition: 'transform 200ms cubic-bezier(0, 0.8, 0.67, 0.97)',
|
||||||
|
display: 'flex',
|
||||||
alignSelf: 'start',
|
alignSelf: 'start',
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
|
|
@ -133,14 +134,31 @@ export const ModernBefore = style({
|
||||||
minWidth: toRem(36),
|
minWidth: toRem(36),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BubbleBefore = style([ModernBefore]);
|
export const BubbleBefore = style({
|
||||||
|
minWidth: toRem(36),
|
||||||
|
});
|
||||||
|
|
||||||
export const BubbleContent = style({
|
export const BubbleContent = style({
|
||||||
maxWidth: toRem(800),
|
maxWidth: toRem(800),
|
||||||
padding: config.space.S200,
|
padding: config.space.S200,
|
||||||
backgroundColor: color.SurfaceVariant.Container,
|
backgroundColor: color.SurfaceVariant.Container,
|
||||||
color: color.SurfaceVariant.OnContainer,
|
color: color.SurfaceVariant.OnContainer,
|
||||||
borderRadius: config.radii.R400,
|
borderRadius: config.radii.R500,
|
||||||
|
position: 'relative',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const BubbleContentArrowLeft = style({
|
||||||
|
borderTopLeftRadius: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const BubbleLeftArrow = style({
|
||||||
|
width: toRem(9),
|
||||||
|
height: toRem(8),
|
||||||
|
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: toRem(-8),
|
||||||
|
zIndex: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Username = style({
|
export const Username = style({
|
||||||
|
|
|
||||||
|
|
@ -723,6 +723,7 @@ export const Message = as<'div', MessageProps>(
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
const senderId = mEvent.getSender() ?? '';
|
const senderId = mEvent.getSender() ?? '';
|
||||||
|
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
const { hoverProps } = useHover({ onHoverChange: setHover });
|
const { hoverProps } = useHover({ onHoverChange: setHover });
|
||||||
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
|
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
|
||||||
|
|
@ -790,7 +791,9 @@ export const Message = as<'div', MessageProps>(
|
||||||
);
|
);
|
||||||
|
|
||||||
const avatarJSX = !collapse && messageLayout !== MessageLayout.Compact && (
|
const avatarJSX = !collapse && messageLayout !== MessageLayout.Compact && (
|
||||||
<AvatarBase>
|
<AvatarBase
|
||||||
|
className={messageLayout === MessageLayout.Bubble ? css.BubbleAvatarBase : undefined}
|
||||||
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
className={css.MessageAvatar}
|
className={css.MessageAvatar}
|
||||||
as="button"
|
as="button"
|
||||||
|
|
@ -875,7 +878,9 @@ export const Message = as<'div', MessageProps>(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessageBase
|
<MessageBase
|
||||||
className={classNames(css.MessageBase, className)}
|
className={classNames(css.MessageBase, className, {
|
||||||
|
[css.MessageBaseBubbleCollapsed]: messageLayout === MessageLayout.Bubble && collapse,
|
||||||
|
})}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
space={messageSpacing}
|
space={messageSpacing}
|
||||||
collapse={collapse}
|
collapse={collapse}
|
||||||
|
|
@ -1132,8 +1137,7 @@ export const Message = as<'div', MessageProps>(
|
||||||
</CompactLayout>
|
</CompactLayout>
|
||||||
)}
|
)}
|
||||||
{messageLayout === MessageLayout.Bubble && (
|
{messageLayout === MessageLayout.Bubble && (
|
||||||
<BubbleLayout before={avatarJSX} onContextMenu={handleContextMenu}>
|
<BubbleLayout before={avatarJSX} header={headerJSX} onContextMenu={handleContextMenu}>
|
||||||
{headerJSX}
|
|
||||||
{msgContentJSX}
|
{msgContentJSX}
|
||||||
</BubbleLayout>
|
</BubbleLayout>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import { DefaultReset, config, toRem } from 'folds';
|
||||||
export const MessageBase = style({
|
export const MessageBase = style({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
});
|
});
|
||||||
|
export const MessageBaseBubbleCollapsed = style({
|
||||||
|
paddingTop: 0,
|
||||||
|
});
|
||||||
|
|
||||||
export const MessageOptionsBase = style([
|
export const MessageOptionsBase = style([
|
||||||
DefaultReset,
|
DefaultReset,
|
||||||
|
|
@ -21,6 +24,10 @@ export const MessageOptionsBar = style([
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const BubbleAvatarBase = style({
|
||||||
|
paddingTop: 0,
|
||||||
|
});
|
||||||
|
|
||||||
export const MessageAvatar = style({
|
export const MessageAvatar = style({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
import { recipe } from '@vanilla-extract/recipes';
|
import { recipe } from '@vanilla-extract/recipes';
|
||||||
import { color, config, DefaultReset, toRem } from 'folds';
|
import { color, config, DefaultReset, toRem } from 'folds';
|
||||||
|
import { ContainerColor } from './ContainerColor.css';
|
||||||
|
|
||||||
export const MarginSpaced = style({
|
export const MarginSpaced = style({
|
||||||
marginBottom: config.space.S200,
|
marginBottom: config.space.S200,
|
||||||
|
|
@ -92,11 +93,14 @@ export const CodeBlock = style([
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
export const CodeBlockHeader = style({
|
export const CodeBlockHeader = style([
|
||||||
|
ContainerColor({ variant: 'Surface' }),
|
||||||
|
{
|
||||||
padding: `0 ${config.space.S200} 0 ${config.space.S300}`,
|
padding: `0 ${config.space.S200} 0 ${config.space.S300}`,
|
||||||
borderBottomWidth: config.borderWidth.B300,
|
borderBottomWidth: config.borderWidth.B300,
|
||||||
gap: config.space.S200,
|
gap: config.space.S200,
|
||||||
});
|
},
|
||||||
|
]);
|
||||||
export const CodeBlockInternal = style([
|
export const CodeBlockInternal = style([
|
||||||
CodeFont,
|
CodeFont,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue