hidden shit, and online status bars, search settings

This commit is contained in:
2026-07-12 07:49:17 +10:00
parent 5374c10c61
commit 02d96a9758
31 changed files with 1323 additions and 265 deletions

View File

@@ -1,35 +1,76 @@
import React, { ReactNode } from 'react';
import React, { CSSProperties, ReactElement, cloneElement, isValidElement } from 'react';
import classNames from 'classnames';
import * as css from './styles.css';
import { config } from 'folds';
import { Presence } from '../../hooks/useUserPresence';
type PresenceAvatarProps = {
/** The presence state to display */
presence?: Presence;
/** The avatar element to wrap */
children: ReactNode;
/** Additional className */
children: ReactElement;
className?: string;
};
const PRESENCE_COLOR: Record<Presence, string> = {
[Presence.Online]: '#38842b',
[Presence.Unavailable]: '#959e30',
[Presence.Offline]: '#454545',
};
const rowStyle: CSSProperties = {
display: 'inline-flex',
flexDirection: 'row',
alignItems: 'stretch',
flexShrink: 0,
lineHeight: 0,
};
const faceStyle: CSSProperties = {
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
// Keep the right side rounded to match size-200 nav avatars.
borderTopRightRadius: config.radii.R400,
borderBottomRightRadius: config.radii.R400,
};
/**
* Wraps an avatar with a left-side presence indicator border.
* Shows green for online, yellow/orange for unavailable/away, grey for offline.
* Presence strip as a real sibling (not ::before).
* Folds Avatar uses overflow:hidden, which clips outside ::before bars.
*/
export function PresenceAvatar({ presence, children, className }: PresenceAvatarProps) {
if (!presence) {
return <>{children}</>;
}
export function PresenceAvatar({
presence = Presence.Offline,
children,
className,
}: PresenceAvatarProps) {
const stripColor = PRESENCE_COLOR[presence] ?? PRESENCE_COLOR[Presence.Offline];
const face = isValidElement(children)
? cloneElement(children, {
className: classNames((children.props as { className?: string }).className),
style: {
...((children.props as { style?: CSSProperties }).style ?? {}),
...faceStyle,
},
// Avoid the folds radii shorthand fighting the square-left corners.
radii: '0',
} as Partial<unknown>)
: children;
return (
<div
className={classNames(css.PresenceAvatarContainer, css.PresenceIndicator, className, {
[css.PresenceOnline]: presence === Presence.Online,
[css.PresenceUnavailable]: presence === Presence.Unavailable,
[css.PresenceOffline]: presence === Presence.Offline,
})}
>
{children}
<div className={className} style={rowStyle} data-presence={presence}>
<span
aria-hidden
style={{
display: 'block',
width: 4,
minWidth: 4,
flex: '0 0 4px',
alignSelf: 'stretch',
// Match folds Avatar size="200" so the strip can't collapse to 0 height.
minHeight: config.size.X200,
borderRadius: '5px 0 0 5px',
backgroundColor: stripColor,
}}
/>
{face}
</div>
);
}

View File

@@ -1,5 +1,5 @@
import { style } from '@vanilla-extract/css';
import { config, color } from 'folds';
import { config } from 'folds';
export const AvatarPresence = style({
display: 'flex',
@@ -21,40 +21,67 @@ export const AvatarPresenceBadge = style({
overflow: 'hidden',
});
export const PresenceAvatarContainer = style({
display: 'flex',
/**
* DM list avatar with flush left presence strip — same approach as message timeline.
* Square left corners; ::before is the colored strip with outer radius.
*/
export const PresenceAvatarFace = style({
position: 'relative',
flexShrink: 0,
alignSelf: 'flex-start',
// Room for the strip so overflow clipping / parent padding doesn't hide it.
marginLeft: '4px',
overflow: 'visible',
borderTopLeftRadius: '0 !important',
borderBottomLeftRadius: '0 !important',
'::before': {
content: '""',
position: 'absolute',
left: 0,
top: '10%',
height: '80%',
width: '3px',
borderTopRightRadius: '2px',
borderBottomRightRadius: '2px',
left: '-4px',
top: 0,
height: '100%',
width: '4px',
borderTopLeftRadius: '5px',
borderBottomLeftRadius: '5px',
zIndex: 1,
backgroundColor: '#454545',
},
selectors: {
'& > *': {
borderTopLeftRadius: '0 !important',
borderBottomLeftRadius: '0 !important',
},
},
});
export const PresenceIndicator = style({});
export const PresenceOnline = style({
'::before': {
backgroundColor: color.Success.Main,
selectors: {
'&::before': {
backgroundColor: '#38842b',
},
},
});
export const PresenceUnavailable = style({
'::before': {
backgroundColor: color.Warning.Main,
selectors: {
'&::before': {
backgroundColor: '#959e30',
},
},
});
export const PresenceOffline = style({
'::before': {
backgroundColor: color.Secondary.Main,
selectors: {
'&::before': {
backgroundColor: '#454545',
},
},
});
/** @deprecated aliases kept for older imports */
export const PresenceAvatarContainer = PresenceAvatarFace;
export const PresenceAvatarRow = PresenceAvatarFace;
export const PresenceStrip = style({});
export const PresenceStripOnline = PresenceOnline;
export const PresenceStripUnavailable = PresenceUnavailable;
export const PresenceStripOffline = PresenceOffline;
export const PresenceIndicator = style({});