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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user