mobile-first UI redesign — touch targets, responsive layout, safe areas

- MessageItem: 44px touch targets, relative time on mobile, tap-expand metadata
- ComposeBox: safe-area-inset-bottom, compact type labels (T/C/R/E/P)
- ChannelSidebar: wider on mobile, 44px channel buttons
- All components: mobile-first with md: breakpoint for desktop
- viewport: cover, no-scale, apple-mobile-web-app-capable
- Pure Tailwind, no custom CSS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 19:35:44 +02:00
parent 98086b7ce7
commit 69e8384598
4 changed files with 124 additions and 108 deletions

View File

@@ -9,12 +9,12 @@ interface Props {
onMessageSent: () => void;
}
const TYPES: { value: MessageType; label: string; key: string }[] = [
{ value: "text", label: "TXT", key: "1" },
{ value: "code", label: "CODE", key: "2" },
{ value: "result", label: "RES", key: "3" },
{ value: "error", label: "ERR", key: "4" },
{ value: "plan", label: "PLAN", key: "5" },
const TYPES: { value: MessageType; label: string; shortLabel: string }[] = [
{ value: "text", label: "TXT", shortLabel: "T" },
{ value: "code", label: "CODE", shortLabel: "C" },
{ value: "result", label: "RES", shortLabel: "R" },
{ value: "error", label: "ERR", shortLabel: "E" },
{ value: "plan", label: "PLAN", shortLabel: "P" },
];
export function ComposeBox({
@@ -46,40 +46,42 @@ export function ComposeBox({
}
return (
<div className="border-t border-border bg-card px-4 py-3">
<div className="border-t border-border bg-card px-3 py-2 md:px-4 md:py-3 pb-[env(safe-area-inset-bottom,8px)]">
{replyTo && (
<div className="flex items-center gap-2 mb-2 text-[11px] text-muted-foreground">
<span>replying to #{replyTo.slice(0, 8)}</span>
<div className="flex items-center gap-2 mb-1.5 text-[10px] md:text-[11px] text-muted-foreground">
<span>^ #{replyTo.slice(0, 8)}</span>
<button
type="button"
onClick={onClearReply}
className="text-[10px] hover:text-foreground"
className="hover:text-foreground min-w-[44px] min-h-[32px] flex items-center justify-center md:min-w-0 md:min-h-0"
>
[x]
</button>
</div>
)}
<div className="flex items-center gap-2">
{/* Type selector */}
<div className="flex items-center gap-1.5 md:gap-2">
{/* Type selector — compact on mobile */}
<div className="flex gap-0.5">
{TYPES.map((t) => (
<button
type="button"
key={t.value}
onClick={() => setMsgType(t.value)}
className={`px-1.5 py-0.5 text-[10px] font-bold rounded-sm transition-colors ${
className={`px-1 md:px-1.5 py-1 md:py-0.5 text-[9px] md:text-[10px] font-bold rounded-sm transition-colors min-w-[28px] md:min-w-0 min-h-[36px] md:min-h-0 flex items-center justify-center ${
msgType === t.value
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
title={`${t.label} (Alt+${t.key})`}
>
{t.label}
{/* Short label on mobile, full on desktop */}
<span className="md:hidden">{t.shortLabel}</span>
<span className="hidden md:inline">{t.label}</span>
</button>
))}
</div>
{/* Input */}
{/* Input — larger touch target on mobile */}
<input
type="text"
value={content}
@@ -89,23 +91,22 @@ export function ComposeBox({
e.preventDefault();
handleSend();
}
// Alt+1-5 for type switching
if (e.altKey && e.key >= "1" && e.key <= "5") {
setMsgType(TYPES[parseInt(e.key) - 1].value);
}
}}
placeholder={`message #${channelId.slice(0, 8)}...`}
placeholder="message..."
disabled={sending}
className="flex-1 bg-input text-[13px] text-foreground placeholder:text-muted-foreground px-3 py-1.5 rounded-sm border border-border focus:outline-none focus:border-[var(--color-agent-glow)]"
className="flex-1 bg-input text-[13px] md:text-[13px] text-foreground placeholder:text-muted-foreground px-3 py-2 md:py-1.5 rounded-sm border border-border focus:outline-none focus:border-blue-500/30"
/>
<button
type="button"
onClick={handleSend}
disabled={sending || !content.trim()}
className="px-3 py-1.5 text-[11px] font-bold bg-primary text-primary-foreground rounded-sm hover:opacity-80 disabled:opacity-30 transition-opacity"
className="px-3 py-2 md:py-1.5 text-[11px] font-bold bg-primary text-primary-foreground rounded-sm hover:opacity-80 disabled:opacity-30 transition-opacity min-w-[44px] min-h-[36px] md:min-h-0 flex items-center justify-center"
>
SEND
{sending ? "..." : "SEND"}
</button>
</div>
</div>