vojo/src/app/components/user-profile/styles.css.ts

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),
});