Touch-target pass: 44px min on default-size buttons (WCAG 2.5.5)
Applies .mobile-touch-target to 32 default-size <Button> elements (32px tall, below the mobile minimum) across 9 files for strict WCAG 2.5.5 compliance: Save, Cancel, Delete, Validate SSH, Run job, Build index, Update connection, Add service, etc. Plus the shared DialogFooter Cancel + Confirm buttons (used by every ConfirmDialog). The class applies min-height/min-width: 44px only below md (max-width: 767px); no-op at md+, so desktop sizing is unchanged. Completes the touch-target audit started in Slice 9 (which covered icon buttons, size=sm buttons, checkboxes, switches). 122 tests pass; lint/ build green. No new tests (@media queries aren't honored by jsdom). Refs openspec/changes/mobile-responsive-parity/verify-report.md residual risk #2.
This commit is contained in:
@@ -50,7 +50,11 @@ export function DialogFooter({
|
||||
}: DialogFooterProps) {
|
||||
return (
|
||||
<div className="flex flex-row flex-wrap items-center justify-end gap-2">
|
||||
<Button variant="ghost" onClick={onCancel}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="mobile-touch-target"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
{secondaryAction ? (
|
||||
@@ -59,6 +63,7 @@ export function DialogFooter({
|
||||
</div>
|
||||
) : null}
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
variant={resolveConfirmVariant(confirmColor, confirmVariant)}
|
||||
disabled={confirmDisabled}
|
||||
onClick={onConfirm}
|
||||
|
||||
@@ -332,10 +332,10 @@ export function WidgetConfigDialog({ open, onClose }: Props) {
|
||||
/>
|
||||
{!isMobile ? (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={reset}>
|
||||
<Button variant="outline" onClick={reset} className="mobile-touch-target">
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={saveDraft} disabled={saveWidget.isPending}>
|
||||
<Button onClick={saveDraft} disabled={saveWidget.isPending} className="mobile-touch-target">
|
||||
Save widget
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -266,7 +266,7 @@ function TaskDialog({
|
||||
confirmBusyLabel="Save action"
|
||||
secondaryAction={
|
||||
onDelete ? (
|
||||
<Button variant="destructive" onClick={onDelete}>
|
||||
<Button variant="destructive" onClick={onDelete} className="mobile-touch-target">
|
||||
Delete
|
||||
</Button>
|
||||
) : undefined
|
||||
@@ -412,13 +412,13 @@ export function Actions() {
|
||||
description="Open the editor popup to modify this action."
|
||||
action={
|
||||
<div className="flex flex-row flex-wrap items-center gap-2">
|
||||
<Button
|
||||
<Button className="mobile-touch-target"
|
||||
variant="outline"
|
||||
onClick={() => openEdit(initialFromTask(editingTask))}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
<Button className="mobile-touch-target"
|
||||
disabled={runTask.isPending || !runServiceId}
|
||||
onClick={async () => {
|
||||
await runTask.mutateAsync({
|
||||
@@ -515,7 +515,7 @@ export function Actions() {
|
||||
description="Select a saved action from the list on the left to view its details, run it, or open the editor popup. Use the button at the bottom to add a new action."
|
||||
>
|
||||
{tasks[0] && (
|
||||
<Button variant="outline" onClick={() => setTab(tasks[0].id)}>
|
||||
<Button variant="outline" onClick={() => setTab(tasks[0].id)} className="mobile-touch-target">
|
||||
Select first action
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -429,10 +429,20 @@ function ShortcutCard({
|
||||
>
|
||||
Open
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={onEdit} className="mobile-touch-target">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onEdit}
|
||||
className="mobile-touch-target"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button size="sm" variant="destructive" onClick={onDelete} className="mobile-touch-target">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={onDelete}
|
||||
className="mobile-touch-target"
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
@@ -503,10 +513,18 @@ export function Dashboard() {
|
||||
description="Quick links to websites today, with room for action and user shortcuts later."
|
||||
action={
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setWidgetDialogOpen(true)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="mobile-touch-target"
|
||||
onClick={() => setWidgetDialogOpen(true)}
|
||||
>
|
||||
Edit dashboard
|
||||
</Button>
|
||||
<Button variant="outline" onClick={openCreateShortcut}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="mobile-touch-target"
|
||||
onClick={openCreateShortcut}
|
||||
>
|
||||
Add shortcut
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -718,14 +718,14 @@ export function FileBrowser() {
|
||||
<div className="flex flex-col gap-2 md:flex-row md:items-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full md:w-auto"
|
||||
className="w-full md:w-auto mobile-touch-target"
|
||||
onClick={() => navigate(pathInput || "/")}
|
||||
>
|
||||
Open
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full md:w-auto"
|
||||
className="w-full md:w-auto mobile-touch-target"
|
||||
onClick={() => refetch()}
|
||||
>
|
||||
Refresh
|
||||
@@ -837,7 +837,7 @@ export function FileBrowser() {
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 md:col-span-3 md:flex-row md:items-end">
|
||||
<Button
|
||||
<Button className="mobile-touch-target"
|
||||
disabled={!selectedJob || runJob.isPending}
|
||||
onClick={() =>
|
||||
runJob.mutate({
|
||||
|
||||
@@ -485,7 +485,7 @@ export function Media() {
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Button
|
||||
<Button className="mobile-touch-target"
|
||||
variant="outline"
|
||||
onClick={() => buildIndex.mutate()}
|
||||
disabled={
|
||||
@@ -496,7 +496,7 @@ export function Media() {
|
||||
</Button>
|
||||
{buildRunning && (
|
||||
<>
|
||||
<Button
|
||||
<Button className="mobile-touch-target"
|
||||
variant="destructive"
|
||||
onClick={() => stopBuildIndex.mutate()}
|
||||
disabled={stopBuildIndex.isPending || buildCancelRequested}
|
||||
@@ -507,7 +507,7 @@ export function Media() {
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-chart-3/40 text-chart-3 hover:bg-chart-3/10"
|
||||
className="border-chart-3/40 text-chart-3 hover:bg-chart-3/10 mobile-touch-target"
|
||||
onClick={() => forceStopBuildIndex.mutate()}
|
||||
disabled={forceStopBuildIndex.isPending}
|
||||
>
|
||||
|
||||
@@ -212,7 +212,11 @@ export function ServicePage() {
|
||||
<Label htmlFor="service-enabled">Enabled</Label>
|
||||
</div>
|
||||
{configFields}
|
||||
<Button variant="destructive" onClick={() => setDeleteOpen(true)}>
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
variant="destructive"
|
||||
onClick={() => setDeleteOpen(true)}
|
||||
>
|
||||
Delete service
|
||||
</Button>
|
||||
{widgetsCard}
|
||||
@@ -252,10 +256,18 @@ export function ServicePage() {
|
||||
<Label htmlFor="service-enabled">Enabled</Label>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Button onClick={save} disabled={saveService.isPending}>
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
onClick={save}
|
||||
disabled={saveService.isPending}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={() => setDeleteOpen(true)}>
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
variant="destructive"
|
||||
onClick={() => setDeleteOpen(true)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
@@ -392,7 +404,9 @@ function ServiceConnectionFields({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button onClick={handleUpdateConnection}>Update connection</Button>
|
||||
<Button className="mobile-touch-target" onClick={handleUpdateConnection}>
|
||||
Update connection
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ function CreateServiceDialog({
|
||||
{!draft ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
{types.map((t) => (
|
||||
<Button
|
||||
<Button className="mobile-touch-target"
|
||||
key={t.service_type}
|
||||
variant="outline"
|
||||
onClick={() => setDraft(emptyDraft(t.service_type))}
|
||||
@@ -287,7 +287,7 @@ export function ServicesPage() {
|
||||
title="Services"
|
||||
description="External services the app talks to. Configure URLs and API keys here; they are encrypted at rest."
|
||||
action={
|
||||
<Button variant="outline" onClick={() => setCreateOpen(true)}>
|
||||
<Button variant="outline" onClick={() => setCreateOpen(true)} className="mobile-touch-target">
|
||||
<Plus className="mr-1 h-3 w-3" />
|
||||
Add service
|
||||
</Button>
|
||||
|
||||
@@ -466,6 +466,7 @@ function MachineEditor({
|
||||
</Alert>
|
||||
<div className="flex flex-row flex-wrap items-center gap-2">
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
variant="outline"
|
||||
onClick={onValidateSSH}
|
||||
disabled={
|
||||
@@ -678,6 +679,7 @@ function SSHKeyManager({
|
||||
</div>
|
||||
<div className="flex flex-row flex-wrap items-center gap-2">
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
disabled={saveKey.isPending}
|
||||
onClick={async () => {
|
||||
await saveKey.mutateAsync(draft);
|
||||
@@ -687,6 +689,7 @@ function SSHKeyManager({
|
||||
{editing ? "Update key" : "Save key"}
|
||||
</Button>
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
variant="outline"
|
||||
disabled={generateKey.isPending}
|
||||
onClick={async () => {
|
||||
@@ -710,11 +713,16 @@ function SSHKeyManager({
|
||||
>
|
||||
{generateKey.isPending ? "Generating..." : "Generate key"}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={clear}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={clear}
|
||||
className="mobile-touch-target"
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
{selectedKey && (
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
variant="destructive"
|
||||
onClick={() => deleteKey.mutate(selectedKey.id)}
|
||||
>
|
||||
@@ -780,7 +788,11 @@ function ResetLocalDatabaseCard() {
|
||||
Reset the local SQLite settings/media index databases after
|
||||
acknowledging the data loss.
|
||||
</p>
|
||||
<Button variant="destructive" onClick={() => setOpen(true)}>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setOpen(true)}
|
||||
className="mobile-touch-target"
|
||||
>
|
||||
Reset local database
|
||||
</Button>
|
||||
{resetDatabase.error && (
|
||||
@@ -1117,6 +1129,7 @@ export function Settings() {
|
||||
</div>
|
||||
<div className="flex flex-row flex-wrap items-center gap-2">
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
openEditMachine(
|
||||
@@ -1144,6 +1157,7 @@ export function Settings() {
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
variant="destructive"
|
||||
onClick={() => setDeleteMachineId(selectedMachine.id)}
|
||||
>
|
||||
@@ -1207,6 +1221,7 @@ export function Settings() {
|
||||
/>
|
||||
{machineDraft.id ? (
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
variant="destructive"
|
||||
onClick={() => setDeleteMachineId(machineDraft.id as string)}
|
||||
>
|
||||
@@ -1266,6 +1281,7 @@ export function Settings() {
|
||||
secondaryAction={
|
||||
machineDraft.id ? (
|
||||
<Button
|
||||
className="mobile-touch-target"
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
setDeleteMachineId(machineDraft.id as string);
|
||||
|
||||
Reference in New Issue
Block a user