style(room): lead the 1:1 stream row with the rail+dot, move the timestamp beside the nick and tint the peer nick lavender
This commit is contained in:
parent
109941e0dd
commit
a84c534179
4 changed files with 150 additions and 158 deletions
|
|
@ -27,7 +27,7 @@ export function EventContent({
|
||||||
}: EventContentProps) {
|
}: EventContentProps) {
|
||||||
const compact = useScreenSizeContext() === ScreenSize.Mobile;
|
const compact = useScreenSizeContext() === ScreenSize.Mobile;
|
||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
const timeRef = useRef<HTMLDivElement>(null);
|
const timeRef = useRef<HTMLSpanElement>(null);
|
||||||
const railRef = useRef<HTMLSpanElement>(null);
|
const railRef = useRef<HTMLSpanElement>(null);
|
||||||
const dotRef = useRef<HTMLSpanElement>(null);
|
const dotRef = useRef<HTMLSpanElement>(null);
|
||||||
const bodyRef = useRef<HTMLDivElement>(null);
|
const bodyRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -49,17 +49,15 @@ export function EventContent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sysline = thin one-line state-event row that lives ON the rail.
|
// Sysline = thin one-line state-event row that lives ON the rail.
|
||||||
// Same 3-track grid as message rows (StreamRoot) — track 1 timestamp,
|
// Same 2-track grid as message rows (StreamRoot) — track 1 dot column,
|
||||||
// track 2 dot column, track 3 body — so the dot's X aligns with the
|
// track 2 body — so the dot's X aligns with the dots above and below it.
|
||||||
// dots above and below it.
|
// The timestamp trails the body (content → time), mirroring the message
|
||||||
|
// header's nick → time order now that the dot leads the row.
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(layoutCss.StreamRoot({ compact }), layoutCss.StreamSysline)}
|
className={classNames(layoutCss.StreamRoot({ compact }), layoutCss.StreamSysline)}
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
>
|
>
|
||||||
<div className={layoutCss.StreamTimeColumn} ref={timeRef}>
|
|
||||||
{time}
|
|
||||||
</div>
|
|
||||||
<span className={layoutCss.StreamDotColumn} aria-hidden>
|
<span className={layoutCss.StreamDotColumn} aria-hidden>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
|
@ -83,6 +81,11 @@ export function EventContent({
|
||||||
<Box gap="200" alignItems="Center" style={{ minWidth: 0 }} ref={bodyRef}>
|
<Box gap="200" alignItems="Center" style={{ minWidth: 0 }} ref={bodyRef}>
|
||||||
<Icon style={{ opacity: 0.5, flexShrink: 0 }} size="50" src={iconSrc} />
|
<Icon style={{ opacity: 0.5, flexShrink: 0 }} size="50" src={iconSrc} />
|
||||||
<div className={layoutCss.StreamSyslineBody}>{content}</div>
|
<div className={layoutCss.StreamSyslineBody}>{content}</div>
|
||||||
|
{time && (
|
||||||
|
<span className={layoutCss.StreamSyslineTime} ref={timeRef}>
|
||||||
|
{time}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { as, toRem } from 'folds';
|
||||||
import * as css from './layout.css';
|
import * as css from './layout.css';
|
||||||
import { useStreamLayoutDebug } from './streamDebug';
|
import { useStreamLayoutDebug } from './streamDebug';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
||||||
import { Time } from '../Time';
|
|
||||||
|
|
||||||
// Day-divider rows fall back to this `S400` MessageBase spacing variant
|
// Day-divider rows fall back to this `S400` MessageBase spacing variant
|
||||||
// (see RoomTimeline.renderDayDivider). Message rows force collapse=true
|
// (see RoomTimeline.renderDayDivider). Message rows force collapse=true
|
||||||
|
|
@ -13,12 +12,6 @@ import { Time } from '../Time';
|
||||||
// over the tighter gap stays hidden by the dot halo.
|
// over the tighter gap stays hidden by the dot halo.
|
||||||
export const STREAM_MESSAGE_SPACING = '400' as const;
|
export const STREAM_MESSAGE_SPACING = '400' as const;
|
||||||
|
|
||||||
// Sample timestamp used by the day-divider's invisible track-1 placeholder.
|
|
||||||
// Any ts works — `<Time compact />` formats with the locale's hh:mm or HH:mm,
|
|
||||||
// and tabular-nums makes every formatted timestamp the same width within a
|
|
||||||
// chat. The auto-sized grid column then matches the surrounding message rows.
|
|
||||||
const DAY_DIVIDER_PLACEHOLDER_TS = 0;
|
|
||||||
|
|
||||||
// Rail-dot diameters. The base dot is 9px (see `StreamDotSize` /
|
// Rail-dot diameters. The base dot is 9px (see `StreamDotSize` /
|
||||||
// `StreamDotColumn` in layout.css.ts, which keep the rail X consistent). The
|
// `StreamDotColumn` in layout.css.ts, which keep the rail X consistent). The
|
||||||
// neutral gray dot is 0.95× that; the «state» dots (green = read, gold =
|
// neutral gray dot is 0.95× that; the «state» dots (green = read, gold =
|
||||||
|
|
@ -32,14 +25,15 @@ const STREAM_DOT_PROMINENT = toRem(9.405);
|
||||||
// Stream layout — DM «VS Code chat» redesign
|
// Stream layout — DM «VS Code chat» redesign
|
||||||
// (docs/plans/dm_stream_vscode_redesign.md).
|
// (docs/plans/dm_stream_vscode_redesign.md).
|
||||||
//
|
//
|
||||||
// Visual structure (3-track CSS grid, see StreamRoot in layout.css.ts):
|
// Visual structure (2-track CSS grid, see StreamRoot in layout.css.ts):
|
||||||
// ┌─G─┬─time─┬─G─┬─dot─┬─G─┬───── bubble (1fr) ─────────┐
|
// ┌─G─┬─dot─┬─G─┬───── content (1fr) ───────────────────┐
|
||||||
|
// nick · time ← header line
|
||||||
|
// bubble / text
|
||||||
//
|
//
|
||||||
// All three gaps come from one `--vojo-stream-gap` custom property — a
|
// The rail + dot lead (track 1), hugging the left screen edge; the nick
|
||||||
// single recipe variant per breakpoint. The browser auto-sizes the time
|
// and a muted timestamp ride the header line at the top of the content
|
||||||
// track to the rendered timestamp width, so 24h "16:58" and AM/PM
|
// column (track 2), in that order — rail+dot → nick → time. The two gaps
|
||||||
// "11:30 PM" both produce a symmetric layout with equal gaps; the dot
|
// (screen→dot, dot→content) come from the StreamRoot recipe per breakpoint.
|
||||||
// and bubble simply shift right when the format is wider.
|
|
||||||
|
|
||||||
export type StreamLayoutProps = {
|
export type StreamLayoutProps = {
|
||||||
time?: ReactNode;
|
time?: ReactNode;
|
||||||
|
|
@ -53,15 +47,16 @@ export type StreamLayoutProps = {
|
||||||
isOwn?: boolean;
|
isOwn?: boolean;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
// Same-sender continuation row (the whole run after the first message, any
|
// Same-sender continuation row (the whole run after the first message, any
|
||||||
// minute): drop the rail dot + timestamp + nick and stack the body tight
|
// minute): drop the rail dot and the whole header line (nick + timestamp)
|
||||||
// under the previous one. The timestamp is kept in the DOM (invisible) only
|
// and stack the body tight under the previous one. The caller also passes
|
||||||
// to reserve the time-track width. The caller also passes `header={undefined}`
|
// `header={undefined}` for collapsed rows — and since the time now rides the
|
||||||
// for collapsed rows. See RoomTimeline `collapsed`.
|
// header line rather than its own grid track, dropping the header drops the
|
||||||
|
// time with it (no width reservation needed). See RoomTimeline `collapsed`.
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
// Author name — rendered as a bold label ABOVE the bubble, on the
|
// Author name — rendered as a bold label ABOVE the bubble, leading the
|
||||||
// dot/timestamp baseline (DM «VS Code chat» redesign). `undefined` for
|
// header line (nick → time) on the dot baseline (DM «VS Code chat»
|
||||||
// media rows (the name is overlaid on the media instead) and for collapsed
|
// redesign). `undefined` for collapsed continuation rows. Media rows still
|
||||||
// continuation rows.
|
// show the nick here too (above the media, no overlay).
|
||||||
header?: ReactNode;
|
header?: ReactNode;
|
||||||
railStart?: boolean;
|
railStart?: boolean;
|
||||||
railEnd?: boolean;
|
railEnd?: boolean;
|
||||||
|
|
@ -100,21 +95,14 @@ export const StreamDayDivider = as<'div', StreamDayDividerProps>(
|
||||||
aria-label={typeof label === 'string' ? label : undefined}
|
aria-label={typeof label === 'string' ? label : undefined}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{/* Track 1: invisible <Time> placeholder so the auto-track sizes
|
{/* Track 1: dot column, holds the rail line + the larger day-dot.
|
||||||
to the same width as adjacent message rows — keeps the rail
|
Same fixed width + paddingLeft anchor as message rows, so the
|
||||||
and day-dot at a consistent X across the timeline. */}
|
day-dot lands on the exact same X as the message dots. */}
|
||||||
<span
|
|
||||||
className={classNames(css.StreamHeaderTime, css.StreamDayTimePlaceholder)}
|
|
||||||
aria-hidden
|
|
||||||
>
|
|
||||||
<Time ts={DAY_DIVIDER_PLACEHOLDER_TS} compact />
|
|
||||||
</span>
|
|
||||||
{/* Track 2: dot column, holds the rail line + the larger day-dot. */}
|
|
||||||
<span className={css.StreamDotColumn} aria-hidden>
|
<span className={css.StreamDotColumn} aria-hidden>
|
||||||
<span className={css.StreamRail} />
|
<span className={css.StreamRail} />
|
||||||
<span className={css.StreamDayDot} />
|
<span className={css.StreamDayDot} />
|
||||||
</span>
|
</span>
|
||||||
{/* Track 3: `─── label ───` line. */}
|
{/* Track 2: `─── label ───` line. */}
|
||||||
<div className={css.StreamDayLineWrap} aria-hidden>
|
<div className={css.StreamDayLineWrap} aria-hidden>
|
||||||
<span className={css.StreamDayLineSegment} />
|
<span className={css.StreamDayLineSegment} />
|
||||||
<span className={css.StreamDayLabel}>{label}</span>
|
<span className={css.StreamDayLabel}>{label}</span>
|
||||||
|
|
@ -180,16 +168,7 @@ export const StreamLayout = as<'div', StreamLayoutProps>(
|
||||||
{...props}
|
{...props}
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
>
|
>
|
||||||
{/* Collapsed rows keep the timestamp in the DOM (so the auto-sized
|
{/* Track 1: rail + dot, leading the row at the left screen edge. */}
|
||||||
time track stays the same width and the body column doesn't shift)
|
|
||||||
but hide it — only the first message of the minute shows its time. */}
|
|
||||||
<span
|
|
||||||
className={classNames(css.StreamHeaderTime, collapsed && css.StreamHeaderTimeHidden)}
|
|
||||||
aria-hidden={collapsed || undefined}
|
|
||||||
ref={timeRef}
|
|
||||||
>
|
|
||||||
{time}
|
|
||||||
</span>
|
|
||||||
<span className={css.StreamDotColumn} aria-hidden>
|
<span className={css.StreamDotColumn} aria-hidden>
|
||||||
{/* The rail is suppressed entirely on trailing continuation rows
|
{/* The rail is suppressed entirely on trailing continuation rows
|
||||||
(after the last dot) so the line stops at the last dot instead of
|
(after the last dot) so the line stops at the last dot instead of
|
||||||
|
|
@ -223,10 +202,23 @@ export const StreamLayout = as<'div', StreamLayoutProps>(
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
{/* Track 2: header line (nick → muted time) + bubble + reactions. */}
|
||||||
<div className={css.StreamColumn}>
|
<div className={css.StreamColumn}>
|
||||||
|
{/* The header prints once at the head of a same-sender run; every
|
||||||
|
continuation row drops it (header is undefined when collapsed), so
|
||||||
|
the nick + timestamp only show on the first message of a run. The
|
||||||
|
nick colour is per-side: own = white/black, peer (vojo) = lavender. */}
|
||||||
{header && (
|
{header && (
|
||||||
<div className={css.StreamName} ref={headerRef}>
|
<div
|
||||||
|
className={classNames(css.StreamName, isOwn ? css.StreamNameOwn : css.StreamNamePeer)}
|
||||||
|
ref={headerRef}
|
||||||
|
>
|
||||||
{header}
|
{header}
|
||||||
|
{time && (
|
||||||
|
<span className={css.StreamHeaderTime} ref={timeRef}>
|
||||||
|
{time}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -141,42 +141,34 @@ export const UsernameBold = style({
|
||||||
// Stream layout (DM «VS Code chat» redesign — see
|
// Stream layout (DM «VS Code chat» redesign — see
|
||||||
// docs/plans/dm_stream_vscode_redesign.md).
|
// docs/plans/dm_stream_vscode_redesign.md).
|
||||||
//
|
//
|
||||||
// Symmetric three-gap layout, expressed as a 3-track CSS grid:
|
// Rail-first layout, expressed as a 2-track CSS grid (the rail + dot hug
|
||||||
|
// the screen edge; the nick and timestamp share the header line):
|
||||||
//
|
//
|
||||||
// ┌─G─┬─time─┬─G─┬─dot─┬─G─┬───── bubble (1fr) ─────────┐
|
// ┌─G─┬─dot─┬─G─┬───── content (1fr) ────────────────────┐
|
||||||
// row row
|
// row │ │ nick · time ← header line row
|
||||||
// left right
|
// left rail │ bubble / text right
|
||||||
//
|
//
|
||||||
// where G = `column-gap` = `padding-left` of the row root. Both inset
|
// where G = `column-gap` = `padding-left` of the row root. Track 1 is the
|
||||||
// values come from the same custom property, so the gap from the row's
|
// dot column (sized to the dot diameter), anchored `G` off the screen edge
|
||||||
// left edge to the timestamp text equals the gap between time and dot
|
// so the rail reads as a margin-rail down the left side. Track 2 (`1fr`) is
|
||||||
// equals the gap between dot and bubble — symmetric by construction, no
|
// the content column: the header line (`nick` then a muted `time`) followed
|
||||||
// per-side magic constants.
|
// by the bubble / plain text. The timestamp is no longer its own grid track
|
||||||
//
|
// — it rides the header line to the right of the nick, so there's no
|
||||||
// Track 1 is `auto`, so the browser sizes it to the rendered timestamp
|
// locale-width auto-track to keep in sync any more.
|
||||||
// width — no JS measurement, no locale branching. 24h "16:58" auto-sizes
|
|
||||||
// to ~33 px and the whole row block contracts left; AM/PM "11:30 PM"
|
|
||||||
// auto-sizes to ~52 px and the block expands right. Both keep the same
|
|
||||||
// `G` gaps; the dot and bubble shift together as one unit.
|
|
||||||
//
|
//
|
||||||
// Two gap dimensions, set per-breakpoint via the `compact` recipe
|
// Two gap dimensions, set per-breakpoint via the `compact` recipe
|
||||||
// variant on the row roots:
|
// variant on the row roots:
|
||||||
//
|
//
|
||||||
// - StreamRowPadVar → screen→time gap (the row's `padding-left`)
|
// - StreamRowPadVar → screen→dot gap (the row's `padding-left`)
|
||||||
// - StreamRowGapVar → time→dot and dot→bubble gaps (the `column-gap`)
|
// - StreamRowGapVar → dot→content gap (the `column-gap`)
|
||||||
//
|
//
|
||||||
// Splitting these lets the inter-element gaps tighten on desktop and
|
// Splitting these lets the dot→content gap tighten on desktop and loosen
|
||||||
// loosen on mobile without disturbing the screen-edge anchor (which the
|
// on mobile without disturbing the screen-edge anchor.
|
||||||
// user dialled in earlier and asked to keep).
|
|
||||||
//
|
//
|
||||||
// The whole time→dot→nick block was nudged ~1mm (~4px) to the right per the
|
// Mobile: pad = S200 (8px off the screen edge); gap = S200.
|
||||||
// latest request — both pad values stepped up one token.
|
// Desktop: pad = S500 (20px — clears the PageNav rail); gap = S500 / 1.1 ≈
|
||||||
// Mobile: pad = S200 (8px — was S100; +4px ≈ 1mm off the screen edge); gap =
|
// 18.2 px (desktop dot→content gap shrunk by 1.1× to keep the layout tight
|
||||||
// S200 (the user asked to double the inter-element gap on native).
|
// without dropping a whole token).
|
||||||
// Desktop: pad = S500 (20px — was S400; +4px ≈ 1mm further from the PageNav,
|
|
||||||
// the column still clears the nav rail); gap = S500 / 1.1 ≈ 18.2 px (the user
|
|
||||||
// asked to shrink the desktop inter-element gap by 1.1× — keeps the layout
|
|
||||||
// tighter without dropping a whole token).
|
|
||||||
const StreamRowPadVar = createVar();
|
const StreamRowPadVar = createVar();
|
||||||
const StreamRowGapVar = createVar();
|
const StreamRowGapVar = createVar();
|
||||||
const StreamRowPadMobile = config.space.S200;
|
const StreamRowPadMobile = config.space.S200;
|
||||||
|
|
@ -218,10 +210,10 @@ export const StreamRoot = recipe({
|
||||||
base: {
|
base: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
// Three tracks: time (auto = formatted-timestamp width) | dot column
|
// Two tracks: dot column (auto = dot diameter) | content (1fr). The dot
|
||||||
// (auto = dot diameter) | bubble (1fr). The grid auto-sizes track 1
|
// column leads so the rail + dot hug the left screen edge; the nick and
|
||||||
// to the rendered timestamp width — locale-agnostic, no JS measure.
|
// timestamp ride the header line inside the content column.
|
||||||
gridTemplateColumns: 'auto auto 1fr',
|
gridTemplateColumns: 'auto 1fr',
|
||||||
columnGap: StreamRowGapVar,
|
columnGap: StreamRowGapVar,
|
||||||
paddingLeft: StreamRowPadVar,
|
paddingLeft: StreamRowPadVar,
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
|
|
@ -269,28 +261,7 @@ export const StreamRoot = recipe({
|
||||||
defaultVariants: { compact: false, collapsed: false },
|
defaultVariants: { compact: false, collapsed: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sysline timestamp. Now a regular grid item in track 1 — sized to its
|
// DotColumn — grid track 1. Always StreamDotSize wide regardless of row
|
||||||
// content width like the message-row timestamp, just inheriting the
|
|
||||||
// row's `align-items: center` so the single-line sysline reads on a
|
|
||||||
// shared centreline with the rest of the row.
|
|
||||||
export const StreamTimeColumn = style({
|
|
||||||
fontSize: toRem(11),
|
|
||||||
fontVariantNumeric: 'tabular-nums',
|
|
||||||
color: color.Surface.OnContainer,
|
|
||||||
opacity: 0.55,
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
fontFamily:
|
|
||||||
'"JetBrains Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
||||||
});
|
|
||||||
|
|
||||||
globalStyle(`${StreamTimeColumn} time`, {
|
|
||||||
display: 'block',
|
|
||||||
fontFamily: 'inherit',
|
|
||||||
fontSize: toRem(11),
|
|
||||||
lineHeight: StreamTimeLineHeight,
|
|
||||||
});
|
|
||||||
|
|
||||||
// DotColumn — grid track 2. Always StreamDotSize wide regardless of row
|
|
||||||
// type so message / sysline / day-divider rows all hit the same vertical
|
// type so message / sysline / day-divider rows all hit the same vertical
|
||||||
// X for the rail line — otherwise the rail would visibly kink at row
|
// X for the rail line — otherwise the rail would visibly kink at row
|
||||||
// boundaries with different dot sizes. Stretches to row content height
|
// boundaries with different dot sizes. Stretches to row content height
|
||||||
|
|
@ -411,12 +382,12 @@ export const StreamDotFill = style({
|
||||||
transition: 'opacity 220ms ease, filter 220ms ease',
|
transition: 'opacity 220ms ease, filter 220ms ease',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wrapper for grid track 3 — holds the bubble and the reactions row stacked.
|
// Wrapper for grid track 2 — holds the header line (nick + time), the bubble
|
||||||
// `min-width: 0` lets fit-content bubbles shrink below their content's natural
|
// and the reactions row stacked. `min-width: 0` lets fit-content bubbles shrink
|
||||||
// size when the grid track is narrower than the bubble (otherwise they'd
|
// below their content's natural size when the grid track is narrower than the
|
||||||
// overflow).
|
// bubble (otherwise they'd overflow).
|
||||||
export const StreamColumn = style({
|
export const StreamColumn = style({
|
||||||
gridColumn: 3,
|
gridColumn: 2,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
|
@ -537,11 +508,13 @@ export const StreamBubble = recipe({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Author name — sits ABOVE the bubble as the first child of StreamColumn
|
// Header line — sits ABOVE the bubble as the first child of StreamColumn
|
||||||
// (track 3), aligned on the dot/timestamp baseline. Bold, a step larger than
|
// (track 2), aligned on the dot baseline. Holds the bold author nick followed
|
||||||
// chat body, pure white (dark) / black (light) via `--vojo-stream-name`. The
|
// by a muted timestamp (rail+dot → nick → time, per the latest request). Bold,
|
||||||
// inner Username button inherits colour/size/weight from here, so callers
|
// a step larger than chat body; the nick colour comes from the own/peer
|
||||||
// pass the name without their own colour/size.
|
// modifier classes below (own = white/black, peer = lavender). The inner Username
|
||||||
|
// button inherits colour/size/weight from here; the timestamp carries its own
|
||||||
|
// explicit muted colour so it does NOT pick up the nick tint.
|
||||||
export const StreamName = style({
|
export const StreamName = style({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
// Symmetric top-left corner (user request): the vertical gap from the nick
|
// Symmetric top-left corner (user request): the vertical gap from the nick
|
||||||
|
|
@ -554,34 +527,48 @@ export const StreamName = style({
|
||||||
lineHeight: StreamNameLineHeight,
|
lineHeight: StreamNameLineHeight,
|
||||||
minHeight: StreamNameLineHeight,
|
minHeight: StreamNameLineHeight,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: 'var(--vojo-stream-name)',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: toRem(6),
|
gap: toRem(8),
|
||||||
flexWrap: 'nowrap',
|
flexWrap: 'nowrap',
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
// Bound the label to the message column so a long display name truncates
|
// Bound the label to the message column so a long display name truncates
|
||||||
// (ellipsis) instead of overflowing the rail. StreamColumn uses
|
// (ellipsis) instead of overflowing the row. StreamColumn uses
|
||||||
// `align-items: flex-start`, so without this the flex container would
|
// `align-items: flex-start`, so without this the flex container would
|
||||||
// content-size past the column edge.
|
// content-size past the column edge.
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Let the (single) name child shrink so css.Username's ellipsis engages on
|
// Own / peer nick colour. Applied alongside StreamName so the inner Username
|
||||||
// long display names — replaces the old `<Text truncate>` wrapper. Must be a
|
// inherits the tint. Own = the original neutral white (dark) / black (light);
|
||||||
// globalStyle: vanilla-extract `style()` selectors may only target `&`.
|
// peer (the vojo side) = lavender, the Dawn brand accent. Both bind to CSS
|
||||||
|
// vars (see index.css) so they stay tunable.
|
||||||
|
export const StreamNameOwn = style({
|
||||||
|
color: 'var(--vojo-stream-name-own)',
|
||||||
|
});
|
||||||
|
export const StreamNamePeer = style({
|
||||||
|
color: 'var(--vojo-stream-name-peer)',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Let the nick child shrink so css.Username's ellipsis engages on long display
|
||||||
|
// names — replaces the old `<Text truncate>` wrapper. Must be a globalStyle:
|
||||||
|
// vanilla-extract `style()` selectors may only target `&`. The timestamp child
|
||||||
|
// opts out of shrinking via `flex-shrink: 0` in StreamHeaderTime below.
|
||||||
globalStyle(`${StreamName} > *`, {
|
globalStyle(`${StreamName} > *`, {
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Message-row timestamp — grid item in track 1, content-sized. Pushed
|
// Header timestamp — inline on the header line, to the RIGHT of the nick
|
||||||
// down by the same Y as StreamHeaderDotHalo so timestamp / dot / Username
|
// (rail+dot → nick → time). Centred against the taller nick by the header
|
||||||
// share one baseline.
|
// row's `align-items: center`, so no manual baseline offset is needed. Carries
|
||||||
|
// its own explicit muted colour so the nick's green/lavender tint does not
|
||||||
|
// bleed onto it, and `flex-shrink: 0` so the nick truncates first.
|
||||||
export const StreamHeaderTime = style({
|
export const StreamHeaderTime = style({
|
||||||
paddingTop: `calc(${StreamHeaderInnerCenterY} - (${StreamTimeLineHeight} / 2))`,
|
flexShrink: 0,
|
||||||
fontSize: toRem(11),
|
fontSize: toRem(11),
|
||||||
lineHeight: StreamTimeLineHeight,
|
lineHeight: StreamTimeLineHeight,
|
||||||
fontVariantNumeric: 'tabular-nums',
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
fontWeight: 400,
|
||||||
color: color.Surface.OnContainer,
|
color: color.Surface.OnContainer,
|
||||||
opacity: 0.55,
|
opacity: 0.55,
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
|
|
@ -596,17 +583,10 @@ globalStyle(`${StreamHeaderTime} time`, {
|
||||||
lineHeight: StreamTimeLineHeight,
|
lineHeight: StreamTimeLineHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Collapsed continuation rows keep the timestamp in the DOM so the auto-sized
|
|
||||||
// time track stays the same width (body column doesn't shift) but render it
|
|
||||||
// invisible — a same-sender run shows its dot+timestamp only on the first row.
|
|
||||||
export const StreamHeaderTimeHidden = style({
|
|
||||||
visibility: 'hidden',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sysline — thin single-line state-event row inside Stream layout.
|
// Sysline — thin single-line state-event row inside Stream layout.
|
||||||
// Composes with StreamRoot so the time / dot / content tracks line up
|
// Composes with StreamRoot so the dot / content tracks line up vertically
|
||||||
// vertically with message rows above and below. Override align-items to
|
// with message rows above and below (dot column | body). Override align-items
|
||||||
// center because the sysline content is one line tall and reads best
|
// to center because the sysline content is one line tall and reads best
|
||||||
// vertically centred.
|
// vertically centred.
|
||||||
export const StreamSysline = style({
|
export const StreamSysline = style({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
@ -629,16 +609,38 @@ export const StreamSyslineBody = style({
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Day divider row — uses the SAME 3-track grid as message rows so its
|
// Sysline timestamp — trailing the body on the same line (content → time),
|
||||||
// dot and rail land on the exact same X as the dots/rails above and below
|
// mirroring the message header's nick → time order now that the dot leads the
|
||||||
// it (otherwise the rail would visibly kink at the day boundary). Track 1
|
// row. Muted mono, never shrinks, so the italic body truncates first.
|
||||||
// holds an invisible `<Time>` placeholder rendered by Stream.tsx so the
|
export const StreamSyslineTime = style({
|
||||||
// `auto` track sizes to the same width as the surrounding message rows.
|
flexShrink: 0,
|
||||||
|
fontSize: toRem(11),
|
||||||
|
lineHeight: StreamTimeLineHeight,
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
color: color.Surface.OnContainer,
|
||||||
|
opacity: 0.45,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
fontFamily:
|
||||||
|
'"JetBrains Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${StreamSyslineTime} time`, {
|
||||||
|
display: 'block',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
fontSize: toRem(11),
|
||||||
|
lineHeight: StreamTimeLineHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Day divider row — uses the SAME 2-track grid as message rows (dot column |
|
||||||
|
// content) so its dot and rail land on the exact same X as the dots/rails above
|
||||||
|
// and below it (otherwise the rail would visibly kink at the day boundary).
|
||||||
|
// Track 1 is the dot column (fixed dot width, anchored by the same paddingLeft),
|
||||||
|
// so the dots align by construction — no invisible time placeholder needed.
|
||||||
export const StreamDayRoot = recipe({
|
export const StreamDayRoot = recipe({
|
||||||
base: {
|
base: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: 'auto auto 1fr',
|
gridTemplateColumns: 'auto 1fr',
|
||||||
columnGap: StreamRowGapVar,
|
columnGap: StreamRowGapVar,
|
||||||
paddingLeft: StreamRowPadVar,
|
paddingLeft: StreamRowPadVar,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
@ -668,17 +670,6 @@ export const StreamDayRoot = recipe({
|
||||||
defaultVariants: { compact: false },
|
defaultVariants: { compact: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Off-screen placeholder timestamp inside the day divider's track 1 —
|
|
||||||
// invisible, but takes the same auto-width as the live timestamps in
|
|
||||||
// surrounding message rows so the grid columns align. Override the
|
|
||||||
// header time's paddingTop because the placeholder needs no header-line
|
|
||||||
// alignment (the day divider is already vertically centred on the row).
|
|
||||||
export const StreamDayTimePlaceholder = style({
|
|
||||||
visibility: 'hidden',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
paddingTop: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StreamDayLabel = style({
|
export const StreamDayLabel = style({
|
||||||
fontSize: toRem(11),
|
fontSize: toRem(11),
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
|
|
@ -710,7 +701,7 @@ export const StreamDayDot = style({
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
// `─── Сегодня ───` line — grid item in track 3. Flex row with two flex:1
|
// `─── Сегодня ───` line — grid item in track 2. Flex row with two flex:1
|
||||||
// hairlines and the label centered between them. The dot's overflow into
|
// hairlines and the label centered between them. The dot's overflow into
|
||||||
// the columnGap means the leftmost line segment naturally starts past
|
// the columnGap means the leftmost line segment naturally starts past
|
||||||
// the dot's right edge.
|
// the dot's right edge.
|
||||||
|
|
|
||||||
|
|
@ -34,14 +34,17 @@
|
||||||
/* DM 1-1 «VS Code chat» redesign tokens (see
|
/* DM 1-1 «VS Code chat» redesign tokens (see
|
||||||
docs/plans/dm_stream_vscode_redesign.md). Light-theme defaults here;
|
docs/plans/dm_stream_vscode_redesign.md). Light-theme defaults here;
|
||||||
dark overrides in `.dark-theme`.
|
dark overrides in `.dark-theme`.
|
||||||
* stream-name — author label colour (pure black light / pure white
|
* stream-name-own — own author label colour (pure black light /
|
||||||
dark, per spec).
|
white dark — the original neutral nick colour).
|
||||||
|
* stream-name-peer — peer (vojo) author label colour (lavender, the
|
||||||
|
Dawn brand accent). Both per-side, see StreamName.
|
||||||
* dot-neutral — gray rail dot for unread / in-flight (green = my-read,
|
* dot-neutral — gray rail dot for unread / in-flight (green = my-read,
|
||||||
gold = mention, red = my-failed are folds tokens in
|
gold = mention, red = my-failed are folds tokens in
|
||||||
useDotColor).
|
useDotColor).
|
||||||
NB: the incoming-bubble fill is NOT a var — `StreamBubble` binds it to
|
NB: the incoming-bubble fill is NOT a var — `StreamBubble` binds it to
|
||||||
`color.Surface.Container` so it always matches the composer card. */
|
`color.Surface.Container` so it always matches the composer card. */
|
||||||
--vojo-stream-name: #000000;
|
--vojo-stream-name-own: #000000;
|
||||||
|
--vojo-stream-name-peer: #5b6aff;
|
||||||
--vojo-dot-neutral: #9aa0aa;
|
--vojo-dot-neutral: #9aa0aa;
|
||||||
|
|
||||||
--font-emoji: 'Twemoji_DISABLED';
|
--font-emoji: 'Twemoji_DISABLED';
|
||||||
|
|
@ -68,8 +71,11 @@
|
||||||
--vojo-timeline-rail: #2a2e38;
|
--vojo-timeline-rail: #2a2e38;
|
||||||
|
|
||||||
/* DM 1-1 «VS Code chat» redesign — dark palette. (Incoming-bubble fill
|
/* DM 1-1 «VS Code chat» redesign — dark palette. (Incoming-bubble fill
|
||||||
binds to color.Surface.Container in StreamBubble, not a var here.) */
|
binds to color.Surface.Container in StreamBubble, not a var here.)
|
||||||
--vojo-stream-name: #ffffff;
|
Own nick = white (original neutral), peer (vojo) nick = the #9580ff
|
||||||
|
Dawn brand lavender. */
|
||||||
|
--vojo-stream-name-own: #ffffff;
|
||||||
|
--vojo-stream-name-peer: #9580ff;
|
||||||
--vojo-dot-neutral: #6b7280;
|
--vojo-dot-neutral: #6b7280;
|
||||||
|
|
||||||
--font-secondary: 'InterVariable', var(--font-emoji), sans-serif;
|
--font-secondary: 'InterVariable', var(--font-emoji), sans-serif;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue