feat(desktop): pinned-messages pill in conv header
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
|||||||
UsersIcon,
|
UsersIcon,
|
||||||
VideoIcon,
|
VideoIcon,
|
||||||
} from './icons';
|
} from './icons';
|
||||||
|
import { PinnedMessagesPill } from './PinnedMessagesPill';
|
||||||
|
|
||||||
const PRESENCE_DOT: Record<PresenceState, string> = {
|
const PRESENCE_DOT: Record<PresenceState, string> = {
|
||||||
online: 'bg-emerald-500',
|
online: 'bg-emerald-500',
|
||||||
@@ -30,6 +31,8 @@ interface Props {
|
|||||||
onMediaClick?: () => void;
|
onMediaClick?: () => void;
|
||||||
onProfileClick?: (ev: React.MouseEvent) => void;
|
onProfileClick?: (ev: React.MouseEvent) => void;
|
||||||
onSearchClick?: () => void;
|
onSearchClick?: () => void;
|
||||||
|
pinnedCount?: number;
|
||||||
|
onOpenPinned?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConversationHeader({
|
export function ConversationHeader({
|
||||||
@@ -39,6 +42,8 @@ export function ConversationHeader({
|
|||||||
onMediaClick,
|
onMediaClick,
|
||||||
onProfileClick,
|
onProfileClick,
|
||||||
onSearchClick,
|
onSearchClick,
|
||||||
|
pinnedCount = 0,
|
||||||
|
onOpenPinned,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
return <header className="h-[65px] border-b border-line px-6 py-3" aria-busy="true" />;
|
return <header className="h-[65px] border-b border-line px-6 py-3" aria-busy="true" />;
|
||||||
@@ -49,10 +54,12 @@ export function ConversationHeader({
|
|||||||
<HeaderBar
|
<HeaderBar
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
peerPresence={peerPresence}
|
peerPresence={peerPresence}
|
||||||
|
pinnedCount={pinnedCount}
|
||||||
{...(onInfoClick ? { onInfoClick } : {})}
|
{...(onInfoClick ? { onInfoClick } : {})}
|
||||||
{...(onMediaClick ? { onMediaClick } : {})}
|
{...(onMediaClick ? { onMediaClick } : {})}
|
||||||
{...(onProfileClick ? { onProfileClick } : {})}
|
{...(onProfileClick ? { onProfileClick } : {})}
|
||||||
{...(onSearchClick ? { onSearchClick } : {})}
|
{...(onSearchClick ? { onSearchClick } : {})}
|
||||||
|
{...(onOpenPinned ? { onOpenPinned } : {})}
|
||||||
/>
|
/>
|
||||||
{/* Voice-channel rail rendered separately in ConversationPage so it
|
{/* Voice-channel rail rendered separately in ConversationPage so it
|
||||||
can sit between the message surface and the in-call dock. */}
|
can sit between the message surface and the in-call dock. */}
|
||||||
@@ -67,6 +74,8 @@ interface HeaderBarProps {
|
|||||||
onMediaClick?: () => void;
|
onMediaClick?: () => void;
|
||||||
onProfileClick?: (ev: React.MouseEvent) => void;
|
onProfileClick?: (ev: React.MouseEvent) => void;
|
||||||
onSearchClick?: () => void;
|
onSearchClick?: () => void;
|
||||||
|
pinnedCount?: number;
|
||||||
|
onOpenPinned?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function HeaderBar({
|
function HeaderBar({
|
||||||
@@ -76,6 +85,8 @@ function HeaderBar({
|
|||||||
onMediaClick,
|
onMediaClick,
|
||||||
onProfileClick,
|
onProfileClick,
|
||||||
onSearchClick,
|
onSearchClick,
|
||||||
|
pinnedCount = 0,
|
||||||
|
onOpenPinned,
|
||||||
}: HeaderBarProps) {
|
}: HeaderBarProps) {
|
||||||
const { t } = useTranslation(['app']);
|
const { t } = useTranslation(['app']);
|
||||||
|
|
||||||
@@ -140,6 +151,7 @@ function HeaderBar({
|
|||||||
<div className="flex min-w-0 items-center gap-2">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
{!isDm && <UsersIcon className="h-4 w-4 shrink-0 text-fg-muted" />}
|
{!isDm && <UsersIcon className="h-4 w-4 shrink-0 text-fg-muted" />}
|
||||||
<p className="truncate font-display text-base font-semibold text-fg">{title}</p>
|
<p className="truncate font-display text-base font-semibold text-fg">{title}</p>
|
||||||
|
<PinnedMessagesPill count={pinnedCount} onClick={() => onOpenPinned?.()} />
|
||||||
</div>
|
</div>
|
||||||
<p className="truncate text-xs text-fg-muted">
|
<p className="truncate text-xs text-fg-muted">
|
||||||
{isDm ? (
|
{isDm ? (
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { PinIcon } from './icons';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
count: number;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact chip rendered in the conv header that opens the pinned panel.
|
||||||
|
// Renders nothing when count is 0 so a fresh conv shows no clutter.
|
||||||
|
export function PinnedMessagesPill({ count, onClick }: Props) {
|
||||||
|
if (count === 0) return null;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
className="inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-full border border-line bg-surface-3 px-2.5 text-xs font-medium text-fg-muted transition hover:bg-surface-2 hover:text-fg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
|
||||||
|
title="Angepinnte Nachrichten anzeigen"
|
||||||
|
>
|
||||||
|
<PinIcon className="h-3 w-3 text-accent" />
|
||||||
|
<span>{count} angepinnt</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user