Polish pass 2 — avatar hover, reply buttons, ellipses, a11y

- Unify NoteCard avatar hover between image and fallback variants
- Add aria-labels to compose toolbar buttons (emoji, attach, poll)
- Fix ThreadView inline reply button sizing to match compose post button
- Replace remaining '...' with unicode ellipsis across podcasts, v4v,
  thread, and ancestor chain components
This commit is contained in:
Jure
2026-04-09 15:36:55 +02:00
parent d134702da7
commit 508829c38b
9 changed files with 13 additions and 10 deletions
+3
View File
@@ -306,6 +306,7 @@ export function ComposeBox({ onPublished, onNoteInjected }: { onPublished?: () =
<button
onClick={() => setShowEmoji((v) => !v)}
title="Insert emoji"
aria-label="Insert emoji"
className="text-text-dim hover:text-text text-[16px] transition-colors"
>
@@ -321,6 +322,7 @@ export function ComposeBox({ onPublished, onNoteInjected }: { onPublished?: () =
onClick={handleFilePicker}
disabled={uploading || isPoll}
title="Attach image or video"
aria-label="Attach image or video"
className="text-text-dim hover:text-text text-[16px] transition-colors disabled:opacity-30"
>
+
@@ -328,6 +330,7 @@ export function ComposeBox({ onPublished, onNoteInjected }: { onPublished?: () =
<button
onClick={() => setIsPoll((v) => !v)}
title={isPoll ? "Cancel poll" : "Create poll"}
aria-label={isPoll ? "Cancel poll" : "Create poll"}
className={`text-[16px] transition-colors ${isPoll ? "text-accent" : "text-text-dim hover:text-text"}`}
>
&#9634;&#9634;
+1 -1
View File
@@ -81,7 +81,7 @@ export const NoteCard = memo(function NoteCard({ event, focused, onReplyInThread
<img
src={avatar}
alt={`${name}'s avatar`}
className="w-9 h-9 rounded-sm object-cover bg-bg-raised hover:opacity-80 transition-opacity"
className="w-9 h-9 rounded-sm object-cover bg-bg-raised ring-1 ring-transparent hover:ring-accent/40 transition-all"
loading="lazy"
onError={(e) => {
(e.target as HTMLImageElement).style.display = "none";
+1 -1
View File
@@ -66,7 +66,7 @@ export function PodcastsView() {
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
placeholder="Search podcasts..."
placeholder="Search podcasts"
className="flex-1 bg-bg-raised border border-border rounded-sm px-3 py-1.5 text-[12px] text-text placeholder:text-text-dim focus:outline-none focus:border-accent"
/>
<button
+1 -1
View File
@@ -219,7 +219,7 @@ export function V4VIndicator() {
disabled={boosting}
className="text-[10px] text-accent hover:text-accent-hover px-2 py-0.5 bg-accent/10 rounded-sm disabled:opacity-40"
>
{boosting ? "..." : "boost"}
{boosting ? "" : "boost"}
</button>
</div>
+1 -1
View File
@@ -106,7 +106,7 @@ export const PollWidget = memo(function PollWidget({ event }: { event: NDKEvent
{pollData ? (
<span>{total} {total === 1 ? "vote" : "votes"}</span>
) : (
<span className="animate-pulse">loading votes...</span>
<span className="animate-pulse">loading votes</span>
)}
{isExpired && <span>&#183; Poll ended</span>}
{closedAt && !isExpired && (
+1 -1
View File
@@ -11,7 +11,7 @@ function AncestorCard({ event }: { event: NDKEvent }) {
const { openThread } = useUIStore();
const truncated = event.content.length > 120
? event.content.slice(0, 120) + "..."
? event.content.slice(0, 120) + ""
: event.content;
return (
+1 -1
View File
@@ -64,7 +64,7 @@ function InlineThreadReply({ replyTo, rootEvent, onPublished }: {
value={text}
onChange={(e) => { setText(e.target.value); autoResize(e); }}
onKeyDown={handleKeyDown}
placeholder="Write a reply..."
placeholder="Write a reply"
rows={2}
className="w-full bg-transparent text-text text-[12px] placeholder:text-text-dim resize-none focus:outline-none"
autoFocus
+3 -3
View File
@@ -213,7 +213,7 @@ export function ThreadView() {
value={replyText}
onChange={(e) => { setReplyText(e.target.value); autoResize(e); }}
onKeyDown={handleKeyDown}
placeholder="Write a reply..."
placeholder="Write a reply"
rows={2}
className="w-full bg-transparent text-text text-[12px] placeholder:text-text-dim resize-none focus:outline-none leading-relaxed"
autoFocus
@@ -248,9 +248,9 @@ export function ThreadView() {
<button
onClick={handleRootReply}
disabled={!replyText.trim() || replying}
className="px-2 py-0.5 text-[10px] bg-accent hover:bg-accent-hover text-accent-text transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
className="px-3 py-1 text-[11px] bg-accent hover:bg-accent-hover text-accent-text rounded-sm transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
>
{replySent ? "replied ✓" : replying ? "posting..." : "reply"}
{replySent ? "replied ✓" : replying ? "posting" : "reply"}
</button>
</div>
</div>
+1 -1
View File
@@ -41,7 +41,7 @@ function HistoryRow({ entry }: { entry: V4VHistoryEntry }) {
<div className="text-[9px] text-text-dim mb-1">Recipients:</div>
{entry.recipients.map((r, i) => (
<div key={i} className="flex items-center justify-between text-[10px]">
<span className="text-text-muted truncate">{r.name || r.address.slice(0, 16) + "..."}</span>
<span className="text-text-muted truncate">{r.name || r.address.slice(0, 16) + ""}</span>
<span className="text-text-dim shrink-0 ml-2">{r.sats} sats</span>
</div>
))}