// Shared visual vocabulary: inline SVG icons (stroke-only, currentColor so // they inherit the Dawn palette), the initials avatar, and the command-card // building blocks reused across the login and contacts surfaces. // // Visual canon: «Боты» mockup at // docs/design/new-direct-messages-design/project/stream-v2-dawn.jsx // (BotsDesktop) — Dawn palette, fleet-violet accent, letter avatars, // friendly cards, no terminal styling. import type { ComponentChildren } from 'preact'; export const RefreshIcon = () => ( ); // Triangle warning glyph — leads the WhatsApp-only About card (which // carries `command-card warn` for the amber outline) and re-appears inside // the AboutModal's risk-disclosure callout. Stroke-only so it picks up the // amber tint via `currentColor` in either context. export const WarningIcon = () => ( ); export const PhoneIcon = () => ( ); export const QrIcon = () => ( ); export const SearchIcon = () => ( ); export const BackIcon = () => ( ); export const UserIcon = () => ( ); export const LogoutIcon = () => ( ); // --- Initials avatar -------------------------------------------------------- // Dawn accent rotation (same hues the design mock assigns to bot list // entries). Hash by codepoints so a contact keeps its color across loads. const AVATAR_COLORS = ['#9580ff', '#7ab6d9', '#d4b88a', '#7dd3a8', '#c08e7b', '#a59cff']; const hashString = (s: string): number => { let h = 0; for (let i = 0; i < s.length; i += 1) { h = (h * 31 + s.charCodeAt(i)) | 0; // eslint-disable-line no-bitwise } return Math.abs(h); }; export const avatarColor = (key: string): string => AVATAR_COLORS[hashString(key) % AVATAR_COLORS.length]; export const initialsOf = (name: string | undefined, fallback = '?'): string => { const trimmed = (name ?? '').trim(); if (!trimmed) return fallback; const parts = trimmed.split(/\s+/).filter(Boolean); if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase(); return trimmed.slice(0, 1).toUpperCase(); }; type AvatarProps = { name?: string; colorKey: string; size?: number; src?: string | null }; // Initials always render underneath; the photo (when the MSC4039 download // resolved) layers on top, so a slow or failed download degrades to the // colored-letter look instead of an empty box. export const Avatar = ({ name, colorKey, size = 38, src }: AvatarProps) => ( ); // --- Command card ----------------------------------------------------------- type CommandCardProps = { icon: ComponentChildren; name: string; desc: string; onClick: () => void; danger?: boolean; /** Amber accent — the WhatsApp About card signalling the Meta-ToS risk * disclosure behind it. */ warn?: boolean; disabled?: boolean; spinning?: boolean; }; export const CommandCard = ({ icon, name, desc, onClick, danger, warn, disabled, spinning, }: CommandCardProps) => ( ); // --- Status / notice -------------------------------------------------------- type StatusPillProps = { tone: 'connected' | 'disconnected' | 'checking'; children: ComponentChildren; }; export const StatusPill = ({ tone, children }: StatusPillProps) => ( {children} ); type NoticeProps = { tone: 'error' | 'warn' | 'info'; children: ComponentChildren; onDismiss?: () => void; }; // Inline notice strip — the replacement for the old transcript pane. One // line of feedback right where the action happened, dismissable, no log. export const Notice = ({ tone, children, onDismiss }: NoticeProps) => (