add ARIA labels, pressed states, and fix keyboard visibility across card detail components
This commit is contained in:
@@ -52,6 +52,7 @@ export function AttachmentSection({
|
|||||||
size="icon-xs"
|
size="icon-xs"
|
||||||
onClick={handleAdd}
|
onClick={handleAdd}
|
||||||
className="text-pylon-text-secondary hover:text-pylon-text"
|
className="text-pylon-text-secondary hover:text-pylon-text"
|
||||||
|
aria-label="Add attachment"
|
||||||
>
|
>
|
||||||
<Plus className="size-3.5" />
|
<Plus className="size-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -71,14 +72,14 @@ export function AttachmentSection({
|
|||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => openPath(att.path)}
|
onClick={() => openPath(att.path)}
|
||||||
className="shrink-0 rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-accent/10 hover:text-pylon-accent group-hover/att:opacity-100"
|
className="shrink-0 rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-accent/10 hover:text-pylon-accent group-hover/att:opacity-100 focus-visible:opacity-100"
|
||||||
aria-label="Open attachment"
|
aria-label="Open attachment"
|
||||||
>
|
>
|
||||||
<ExternalLink className="size-3" />
|
<ExternalLink className="size-3" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => removeAttachment(cardId, att.id)}
|
onClick={() => removeAttachment(cardId, att.id)}
|
||||||
className="shrink-0 rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-danger/10 hover:text-pylon-danger group-hover/att:opacity-100"
|
className="shrink-0 rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-danger/10 hover:text-pylon-danger group-hover/att:opacity-100 focus-visible:opacity-100"
|
||||||
aria-label="Remove attachment"
|
aria-label="Remove attachment"
|
||||||
>
|
>
|
||||||
<X className="size-3" />
|
<X className="size-3" />
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ export function ChecklistSection({ cardId, checklist }: ChecklistSectionProps) {
|
|||||||
onChange={(e) => setNewItemText(e.target.value)}
|
onChange={(e) => setNewItemText(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Add item..."
|
placeholder="Add item..."
|
||||||
|
aria-label="New checklist item"
|
||||||
className="h-7 flex-1 rounded-md bg-pylon-column px-2 text-sm text-pylon-text outline-none placeholder:text-pylon-text-secondary/60 focus:ring-1 focus:ring-pylon-accent"
|
className="h-7 flex-1 rounded-md bg-pylon-column px-2 text-sm text-pylon-text outline-none placeholder:text-pylon-text-secondary/60 focus:ring-1 focus:ring-pylon-accent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,7 +177,8 @@ function ChecklistRow({ item, onToggle, onUpdate, onDelete }: ChecklistRowProps)
|
|||||||
{...attributes}
|
{...attributes}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="shrink-0 cursor-grab text-pylon-text-secondary opacity-0 group-hover/item:opacity-100"
|
className="shrink-0 cursor-grab text-pylon-text-secondary opacity-0 group-hover/item:opacity-100 focus-visible:opacity-100"
|
||||||
|
aria-label="Reorder item"
|
||||||
{...listeners}
|
{...listeners}
|
||||||
>
|
>
|
||||||
<GripVertical className="size-3" />
|
<GripVertical className="size-3" />
|
||||||
@@ -185,6 +187,7 @@ function ChecklistRow({ item, onToggle, onUpdate, onDelete }: ChecklistRowProps)
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={item.checked}
|
checked={item.checked}
|
||||||
onChange={onToggle}
|
onChange={onToggle}
|
||||||
|
aria-label={`Mark "${item.text}" as ${item.checked ? "incomplete" : "complete"}`}
|
||||||
className="size-3.5 shrink-0 cursor-pointer accent-pylon-accent"
|
className="size-3.5 shrink-0 cursor-pointer accent-pylon-accent"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -215,7 +218,7 @@ function ChecklistRow({ item, onToggle, onUpdate, onDelete }: ChecklistRowProps)
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
className="shrink-0 rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-danger/10 hover:text-pylon-danger group-hover/item:opacity-100"
|
className="shrink-0 rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-danger/10 hover:text-pylon-danger group-hover/item:opacity-100 focus-visible:opacity-100"
|
||||||
aria-label="Delete item"
|
aria-label="Delete item"
|
||||||
>
|
>
|
||||||
<X className="size-3" />
|
<X className="size-3" />
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export function CommentsSection({ cardId, comments }: CommentsSectionProps) {
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Add a comment... (Enter to send, Shift+Enter for newline)"
|
placeholder="Add a comment... (Enter to send, Shift+Enter for newline)"
|
||||||
rows={2}
|
rows={2}
|
||||||
|
aria-label="Add a comment"
|
||||||
className="flex-1 resize-none rounded-md bg-pylon-column px-2 py-1.5 text-sm text-pylon-text outline-none placeholder:text-pylon-text-secondary/60 focus:ring-1 focus:ring-pylon-accent"
|
className="flex-1 resize-none rounded-md bg-pylon-column px-2 py-1.5 text-sm text-pylon-text outline-none placeholder:text-pylon-text-secondary/60 focus:ring-1 focus:ring-pylon-accent"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@@ -82,7 +83,7 @@ export function CommentsSection({ cardId, comments }: CommentsSectionProps) {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteComment(cardId, comment.id)}
|
onClick={() => deleteComment(cardId, comment.id)}
|
||||||
className="shrink-0 self-start rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-danger/10 hover:text-pylon-danger group-hover/comment:opacity-100"
|
className="shrink-0 self-start rounded p-0.5 text-pylon-text-secondary opacity-0 transition-opacity hover:bg-pylon-danger/10 hover:text-pylon-danger group-hover/comment:opacity-100 focus-visible:opacity-100"
|
||||||
aria-label="Delete comment"
|
aria-label="Delete comment"
|
||||||
>
|
>
|
||||||
<X className="size-3" />
|
<X className="size-3" />
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export function LabelPicker({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-xs"
|
size="icon-xs"
|
||||||
className="text-pylon-text-secondary hover:text-pylon-text"
|
className="text-pylon-text-secondary hover:text-pylon-text"
|
||||||
|
aria-label="Manage labels"
|
||||||
>
|
>
|
||||||
<Plus className="size-3.5" />
|
<Plus className="size-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -95,6 +96,8 @@ export function LabelPicker({
|
|||||||
key={label.id}
|
key={label.id}
|
||||||
onClick={() => toggleCardLabel(cardId, label.id)}
|
onClick={() => toggleCardLabel(cardId, label.id)}
|
||||||
className="flex items-center gap-2 rounded px-2 py-1 text-left text-sm transition-colors hover:bg-pylon-column"
|
className="flex items-center gap-2 rounded px-2 py-1 text-left text-sm transition-colors hover:bg-pylon-column"
|
||||||
|
aria-pressed={isSelected}
|
||||||
|
aria-label={`${isSelected ? "Remove" : "Add"} label: ${label.name}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="size-3 shrink-0 rounded-full"
|
className="size-3 shrink-0 rounded-full"
|
||||||
@@ -122,6 +125,7 @@ export function LabelPicker({
|
|||||||
onChange={(e) => setNewLabelName(e.target.value)}
|
onChange={(e) => setNewLabelName(e.target.value)}
|
||||||
onKeyDown={handleCreateKeyDown}
|
onKeyDown={handleCreateKeyDown}
|
||||||
placeholder="Label name..."
|
placeholder="Label name..."
|
||||||
|
aria-label="New label name"
|
||||||
className="h-7 rounded-md bg-pylon-column px-2 text-sm text-pylon-text outline-none placeholder:text-pylon-text-secondary/60 focus:ring-1 focus:ring-pylon-accent"
|
className="h-7 rounded-md bg-pylon-column px-2 text-sm text-pylon-text outline-none placeholder:text-pylon-text-secondary/60 focus:ring-1 focus:ring-pylon-accent"
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
@@ -130,6 +134,7 @@ export function LabelPicker({
|
|||||||
key={color}
|
key={color}
|
||||||
onClick={() => setNewLabelColor(color)}
|
onClick={() => setNewLabelColor(color)}
|
||||||
className="size-5 rounded-full transition-transform hover:scale-110"
|
className="size-5 rounded-full transition-transform hover:scale-110"
|
||||||
|
aria-label={`Color ${color}`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
outline:
|
outline:
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export function MarkdownEditor({ cardId, value }: MarkdownEditorProps) {
|
|||||||
variant={mode === "edit" ? "secondary" : "ghost"}
|
variant={mode === "edit" ? "secondary" : "ghost"}
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => setMode("edit")}
|
onClick={() => setMode("edit")}
|
||||||
|
aria-pressed={mode === "edit"}
|
||||||
className="font-mono text-xs"
|
className="font-mono text-xs"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
@@ -83,6 +84,7 @@ export function MarkdownEditor({ cardId, value }: MarkdownEditorProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant={mode === "preview" ? "secondary" : "ghost"}
|
variant={mode === "preview" ? "secondary" : "ghost"}
|
||||||
size="xs"
|
size="xs"
|
||||||
|
aria-pressed={mode === "preview"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// Save before switching to preview
|
// Save before switching to preview
|
||||||
if (mode === "edit") {
|
if (mode === "edit") {
|
||||||
@@ -113,6 +115,7 @@ export function MarkdownEditor({ cardId, value }: MarkdownEditorProps) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
placeholder="Add a description... (Markdown supported)"
|
placeholder="Add a description... (Markdown supported)"
|
||||||
|
aria-label="Card description (Markdown)"
|
||||||
className="min-h-[100px] w-full resize-none overflow-hidden bg-transparent px-3 py-2 text-sm text-pylon-text outline-none placeholder:text-pylon-text-secondary/60"
|
className="min-h-[100px] w-full resize-none overflow-hidden bg-transparent px-3 py-2 text-sm text-pylon-text outline-none placeholder:text-pylon-text-secondary/60"
|
||||||
/>
|
/>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export function PriorityPicker({ cardId, priority }: PriorityPickerProps) {
|
|||||||
<button
|
<button
|
||||||
key={value}
|
key={value}
|
||||||
onClick={() => updateCard(cardId, { priority: value })}
|
onClick={() => updateCard(cardId, { priority: value })}
|
||||||
|
aria-pressed={priority === value}
|
||||||
className={`rounded-full px-3 py-1 text-xs font-medium transition-all ${
|
className={`rounded-full px-3 py-1 text-xs font-medium transition-all ${
|
||||||
priority === value
|
priority === value
|
||||||
? "text-white shadow-sm"
|
? "text-white shadow-sm"
|
||||||
|
|||||||
Reference in New Issue
Block a user