fix(ui): mention color picker, send-animation conflict, DM virtualizer (N69,N10,N22)

- N69: @mention highlight color now uses HexColorPickerPopOut + react-colorful
  HexColorPicker behind a folds Button (color swatch); built-in onRemove
  replaces the separate Reset, dropping the OS-native <input type="color">
- N10: mentionPulseKeyframes animates only box-shadow (dropped the imperceptible
  scale(1.003)) so it no longer fights MsgAppearClass over `transform` on
  self-sent @mention messages
- N22: Direct.tsx virtualizer estimateSize 38 -> 52 (two-line DM row height) to
  avoid the initial-render jump before measureElement corrects each row

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 20:54:32 -04:00
parent 5470e25bb0
commit dfd2c9c49e
4 changed files with 45 additions and 25 deletions
@@ -110,10 +110,15 @@ export type MessageBaseVariants = RecipeVariants<typeof MessageBase>;
// ── Mention pulse animation ───────────────────────────────────────────────────
// Animates only `box-shadow` — NOT `transform`. A self-sent @mention message
// carries both this class and `MsgAppearClass` (which animates a scale), and two
// animations on the same element cannot share the `transform` property: the
// later one wins and the other is silently dropped. Pulsing the glow alone keeps
// both effects working. (The previous scale(1.003) was imperceptible anyway.)
const mentionPulseKeyframes = keyframes({
'0%': { transform: 'scale(1)', boxShadow: 'none' },
'30%': { transform: 'scale(1.003)', boxShadow: `0 0 8px ${color.Warning.Main}` },
'100%': { transform: 'scale(1)', boxShadow: 'none' },
'0%': { boxShadow: 'none' },
'30%': { boxShadow: `0 0 8px ${color.Warning.Main}` },
'100%': { boxShadow: 'none' },
});
/**
+29 -18
View File
@@ -32,7 +32,9 @@ import {
toRem,
} from 'folds';
import { isKeyHotkey } from 'is-hotkey';
import { HexColorPicker } from 'react-colorful';
import FocusTrap from 'focus-trap-react';
import { HexColorPickerPopOut } from '../../../components/HexColorPickerPopOut';
import { Page, PageContent, PageHeader } from '../../../components/page';
import { SequenceCard } from '../../../components/sequence-card';
import {
@@ -640,33 +642,42 @@ function Appearance() {
title="@Mention Highlight Color"
description="Color used to highlight messages that mention you. Leave empty to use the theme default."
after={
<Box alignItems="Center" gap="200">
<input
type="color"
value={mentionHighlightColor || '#4caf50'}
onChange={(e) => setMentionHighlightColor(e.target.value)}
style={{
width: '36px',
height: '28px',
cursor: 'pointer',
borderRadius: '4px',
border: 'none',
padding: '2px',
}}
/>
{mentionHighlightColor && (
<HexColorPickerPopOut
picker={
<HexColorPicker
color={mentionHighlightColor || '#4caf50'}
onChange={setMentionHighlightColor}
/>
}
onRemove={mentionHighlightColor ? () => setMentionHighlightColor('') : undefined}
>
{(openPicker, opened) => (
<Button
type="button"
onClick={() => setMentionHighlightColor('')}
aria-pressed={opened}
onClick={openPicker}
size="300"
variant="Secondary"
fill="Soft"
radii="300"
before={
<span
style={{
width: toRem(16),
height: toRem(16),
borderRadius: config.radii.R300,
background: mentionHighlightColor || color.Primary.Main,
border: `${config.borderWidth.B300} solid ${color.SurfaceVariant.ContainerLine}`,
display: 'inline-block',
flexShrink: 0,
}}
/>
}
>
<Text size="B300">Reset</Text>
<Text size="B300">{mentionHighlightColor ? 'Change' : 'Pick'}</Text>
</Button>
)}
</Box>
</HexColorPickerPopOut>
}
/>
</SequenceCard>
+5 -1
View File
@@ -227,7 +227,11 @@ export function Direct() {
const virtualizer = useVirtualizer({
count: filteredDirects.length,
getScrollElement: () => scrollRef.current,
estimateSize: () => 38,
// DM rows render a two-line layout (name + message preview), so they are
// taller than the 38px single-line rooms elsewhere. Estimating the common
// two-line height avoids the initial-render jump/overlap before
// `measureElement` corrects each row to its exact size.
estimateSize: () => 52,
overscan: 10,
});