228 lines
6.4 KiB
TypeScript
228 lines
6.4 KiB
TypeScript
import { style } from '@vanilla-extract/css';
|
|
import { color, config, toRem } from 'folds';
|
|
|
|
// ── Card root ────────────────────────────────────────────────────
|
|
//
|
|
// Establishes the positioning context for the floating top-right
|
|
// 3-dot actions trigger. The card content (hero / info / alerts)
|
|
// sits in normal flow; the trigger is absolute-positioned over it.
|
|
export const CardRoot = style({
|
|
position: 'relative',
|
|
});
|
|
|
|
// 3-dot button anchor — top-right of the card. `right: 0` lines it
|
|
// up flush with the card content's right edge (the panel padding
|
|
// is supplied by the host, so we don't add inset here).
|
|
export const CardActionsAnchor = style({
|
|
position: 'absolute',
|
|
top: 0,
|
|
right: 0,
|
|
zIndex: 1,
|
|
});
|
|
|
|
// ── Hero ─────────────────────────────────────────────────────────
|
|
|
|
export const HeroRoot = style({
|
|
width: '100%',
|
|
paddingTop: toRem(8),
|
|
});
|
|
|
|
// 96px round avatar wrapper. Uses `position: relative` so the
|
|
// online-dot can sit inset at bottom-right with a ring matching the
|
|
// surrounding background. Folds' `Avatar` is overridden globally to
|
|
// be circular, so we just size the wrapper and let the inner avatar
|
|
// fill it.
|
|
export const HeroAvatar = style({
|
|
position: 'relative',
|
|
display: 'inline-flex',
|
|
width: toRem(96),
|
|
height: toRem(96),
|
|
borderRadius: '50%',
|
|
flexShrink: 0,
|
|
// Hairline ring sits on top of the gradient so we get the same
|
|
// «inset border» rhythm as the chat-list rows in the design bundle.
|
|
boxShadow: `0 0 0 ${config.borderWidth.B600} ${color.Background.Container}`,
|
|
});
|
|
|
|
// Linear gradient is set via inline style (so `colorMXID` can drive
|
|
// the base hue). This rule just makes the fallback span fill the
|
|
// avatar circle and centre its initial.
|
|
export const HeroAvatarFallback = style({
|
|
width: '100%',
|
|
height: '100%',
|
|
borderRadius: '50%',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
color: '#fff',
|
|
fontWeight: 600,
|
|
fontSize: toRem(36),
|
|
letterSpacing: '0.5px',
|
|
textTransform: 'uppercase',
|
|
});
|
|
|
|
// Online indicator dot — green pill inset bottom-right of the
|
|
// avatar with a ring matching the parent background so it punches
|
|
// out against the avatar circle, mirroring the design bundle's
|
|
// `.online` glyph.
|
|
export const HeroOnlineDot = style({
|
|
position: 'absolute',
|
|
right: toRem(2),
|
|
bottom: toRem(2),
|
|
width: toRem(16),
|
|
height: toRem(16),
|
|
borderRadius: '50%',
|
|
backgroundColor: color.Success.Main,
|
|
boxShadow: `0 0 0 ${toRem(3)} ${color.Background.Container}`,
|
|
});
|
|
|
|
// Reset native button chrome for the tap-to-zoom avatar wrapper.
|
|
export const HeroAvatarButton = style({
|
|
display: 'inline-flex',
|
|
background: 'transparent',
|
|
border: 'none',
|
|
padding: 0,
|
|
margin: 0,
|
|
cursor: 'pointer',
|
|
});
|
|
|
|
export const HeroIdentity = style({
|
|
width: '100%',
|
|
});
|
|
|
|
export const HeroPresence = style({
|
|
width: '100%',
|
|
justifyContent: 'center',
|
|
});
|
|
|
|
// Coloured «онлайн» tag — mirrors the chat header's `OnlineTag`
|
|
// style so the two surfaces use identical typography for the same
|
|
// signal. Dropping the green dot prefix here is intentional: the
|
|
// avatar already carries the green online indicator, so a second
|
|
// dot would just be visual noise.
|
|
export const HeroOnlineTag = style({
|
|
color: color.Success.Main,
|
|
flexShrink: 0,
|
|
whiteSpace: 'nowrap',
|
|
});
|
|
|
|
export const HeroBullet = style({
|
|
color: color.Surface.ContainerLine,
|
|
});
|
|
|
|
export const HeroE2ee = style({
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: toRem(4),
|
|
color: color.Success.Main,
|
|
});
|
|
|
|
// Quiet «last activity» dot prefix used only on offline / idle
|
|
// presence labels. Online state has no dot here — the avatar's own
|
|
// green indicator serves that role.
|
|
export const PresenceDot = style({
|
|
width: toRem(8),
|
|
height: toRem(8),
|
|
borderRadius: '50%',
|
|
backgroundColor: color.Surface.ContainerLine,
|
|
flexShrink: 0,
|
|
});
|
|
|
|
// ── Info section / rows (Fleet-style attribute table) ────────────
|
|
|
|
export const InfoSection = style({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: toRem(2),
|
|
paddingTop: toRem(8),
|
|
paddingBottom: toRem(8),
|
|
borderTop: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
|
|
borderBottom: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
|
|
});
|
|
|
|
export const InfoRow = style({
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: toRem(12),
|
|
minHeight: toRem(32),
|
|
padding: `${toRem(4)} ${toRem(2)}`,
|
|
});
|
|
|
|
// Fixed-width monospace label column. The `letter-spacing` and
|
|
// uppercase mirror Fleet's panel-attribute look.
|
|
export const InfoRowLabel = style({
|
|
flexShrink: 0,
|
|
width: toRem(72),
|
|
fontFamily: 'ui-monospace, "JetBrains Mono", monospace',
|
|
fontSize: toRem(11),
|
|
letterSpacing: '0.06em',
|
|
textTransform: 'uppercase',
|
|
color: color.Surface.OnContainer,
|
|
opacity: 0.55,
|
|
});
|
|
|
|
export const InfoRowValue = style({
|
|
flex: 1,
|
|
minWidth: 0,
|
|
fontSize: toRem(13),
|
|
color: color.Surface.OnContainer,
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
});
|
|
|
|
// Right-side affordance — copy / open / chevron-menu trigger. Looks
|
|
// like a tiny ghost icon button: transparent, hover-tints in the
|
|
// «hover surface» token, never grows past the row's intrinsic
|
|
// height.
|
|
export const InfoRowTrailing = style({
|
|
flexShrink: 0,
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
});
|
|
|
|
export const InfoRowAction = style({
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
width: toRem(24),
|
|
height: toRem(24),
|
|
borderRadius: toRem(6),
|
|
background: 'transparent',
|
|
border: 'none',
|
|
color: color.Surface.OnContainer,
|
|
cursor: 'pointer',
|
|
opacity: 0.7,
|
|
selectors: {
|
|
'&:hover': {
|
|
opacity: 1,
|
|
background: color.SurfaceVariant.Container,
|
|
},
|
|
'&:disabled': {
|
|
opacity: 0.3,
|
|
cursor: 'default',
|
|
},
|
|
'&[aria-pressed="true"]': {
|
|
opacity: 1,
|
|
background: color.SurfaceVariant.Container,
|
|
},
|
|
},
|
|
});
|
|
|
|
// Variants used inside `InfoRowValue` — accent for creators (star
|
|
// badge), mixed (label + faint mono pl number side by side), mono
|
|
// (just the pl-N tag).
|
|
export const InfoRowAccent = style({
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: toRem(6),
|
|
color: color.Warning.Main,
|
|
fontWeight: 600,
|
|
});
|
|
|
|
export const InfoRowMixed = style({
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: toRem(8),
|
|
});
|
|
|