mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(mobile): close safe-area gaps and lift sub-44px tap targets
Subtract both the top and bottom safe-area insets from the full-page Coach height so the composer no longer slides behind the bottom-nav on a notched phone, and pad the Coach drawer composer and the ResponsiveSheet bottom-sheet footer for the home-indicator band. Raise the cycle and illness day-log severity controls to a 44px touch floor, add hit-slop to the custom-symptom remove button, give the shared day-log chips a min height, and let the mood-tag action cluster wrap so a 360px row no longer clips the tag name.
This commit is contained in:
1 parent
a74a0c9b09
commit
b02e3e4072
6 files changed
+114
-110
No files matched your search
@@ -136,7 +136,7 @@ export default function CoachPage() {
|
||||
// `env(safe-area-inset-bottom)`) so the docked composer / Stop control
|
||||
// always clears the nav instead of sitting under it. On `md+` the nav
|
||||
// is hidden, so the page reclaims the full height below the TopBar.
|
||||
className="bg-background -mx-4 -mt-6 -mb-20 flex h-[calc(100dvh-8rem-env(safe-area-inset-bottom,0px))] min-h-[32rem] flex-col overflow-hidden md:-mx-6 md:h-[calc(100dvh-4rem)]"
|
||||
className="bg-background -mx-4 -mt-6 -mb-20 flex h-[calc(100dvh-8rem-env(safe-area-inset-top,0px)-env(safe-area-inset-bottom,0px))] min-h-[32rem] flex-col overflow-hidden md:-mx-6 md:h-[calc(100dvh-4rem)]"
|
||||
>
|
||||
{/* `useSearchParams` requires a Suspense boundary so the client-search
|
||||
bailout never opts the whole route out of static optimisation. */}
|
||||
|
||||
@@ -423,7 +423,7 @@ function Chip({
|
||||
aria-pressed={active}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"focus-visible:ring-ring/50 rounded-full border px-3 py-1.5 text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
||||
"focus-visible:ring-ring/50 inline-flex min-h-11 items-center rounded-full border px-3 py-1.5 text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none sm:min-h-8",
|
||||
active
|
||||
? "border-primary bg-primary/10 text-primary"
|
||||
: "border-border text-foreground hover:bg-accent",
|
||||
@@ -1225,7 +1225,7 @@ function SymptomChip({
|
||||
onClick={handleRemove}
|
||||
disabled={deleteCustom.isPending}
|
||||
aria-label={t("cycle.symptom.custom.remove", { label })}
|
||||
className="border-border bg-background text-muted-foreground hover:text-destructive hover:border-destructive focus-visible:ring-ring/50 -ml-1.5 grid size-4 shrink-0 place-items-center rounded-full border transition-colors focus-visible:ring-2 focus-visible:outline-none"
|
||||
className="border-border bg-background text-muted-foreground hover:text-destructive hover:border-destructive focus-visible:ring-ring/50 relative -ml-1.5 grid size-4 shrink-0 place-items-center rounded-full border transition-colors before:absolute before:-inset-2 before:content-[''] focus-visible:ring-2 focus-visible:outline-none"
|
||||
>
|
||||
<X className="size-2.5" aria-hidden="true" />
|
||||
</button>
|
||||
@@ -1245,7 +1245,7 @@ function SymptomChip({
|
||||
aria-label={t("cycle.sheet.severityLevel", { level: lvl })}
|
||||
onClick={() => onSeverity(symptomKey, lvl)}
|
||||
className={cn(
|
||||
"focus-visible:ring-ring/50 size-6 rounded-full border text-xs tabular-nums transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
||||
"focus-visible:ring-ring/50 grid size-11 place-items-center rounded-full border text-xs tabular-nums transition-colors focus-visible:ring-2 focus-visible:outline-none sm:size-8",
|
||||
severity === lvl
|
||||
? "border-primary bg-primary/15 text-primary font-semibold"
|
||||
: "border-border text-muted-foreground hover:bg-accent",
|
||||
|
||||
@@ -60,7 +60,7 @@ function Chip({
|
||||
aria-pressed={active}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"rounded-full border px-3 py-1.5 text-sm transition-colors",
|
||||
"inline-flex min-h-11 items-center rounded-full border px-3 py-1.5 text-sm transition-colors sm:min-h-8",
|
||||
active
|
||||
? "border-primary bg-primary/10 text-foreground"
|
||||
: "border-border text-muted-foreground hover:bg-muted",
|
||||
@@ -95,7 +95,7 @@ function SymptomChip({
|
||||
aria-pressed={active}
|
||||
onClick={onToggle}
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm transition-colors",
|
||||
"flex min-h-11 items-center gap-2 rounded-full border px-3 py-1.5 text-sm transition-colors sm:min-h-8",
|
||||
active
|
||||
? "border-primary bg-primary/10 text-foreground"
|
||||
: "border-border text-muted-foreground hover:bg-muted",
|
||||
@@ -117,7 +117,7 @@ function SymptomChip({
|
||||
aria-pressed={severity === value}
|
||||
onClick={() => onSeverity(value)}
|
||||
className={cn(
|
||||
"h-6 w-6 rounded-md border text-xs",
|
||||
"grid h-11 w-11 place-items-center rounded-md border text-xs sm:h-8 sm:w-8",
|
||||
severity === value
|
||||
? "border-primary bg-primary/10 text-foreground"
|
||||
: "border-border text-muted-foreground hover:bg-muted",
|
||||
|
||||
@@ -234,7 +234,7 @@ export function CoachDrawerBody({
|
||||
{/* Composer pinned to the bottom. */}
|
||||
<div
|
||||
data-slot="coach-drawer-composer"
|
||||
className="border-border/70 flex flex-col gap-2 border-t p-3 sm:p-4"
|
||||
className="border-border/70 flex flex-col gap-2 border-t p-3 pb-[calc(env(safe-area-inset-bottom,0px)+0.75rem)] sm:p-4 sm:pb-[calc(env(safe-area-inset-bottom,0px)+1rem)]"
|
||||
>
|
||||
{composer}
|
||||
</div>
|
||||
|
||||
@@ -502,7 +502,7 @@ function SortableTagRow({
|
||||
data-tag={tag.key}
|
||||
data-hidden={isHidden ? "true" : undefined}
|
||||
data-dragging={isDragging ? "true" : undefined}
|
||||
className={`border-border bg-background/30 flex min-h-12 items-center gap-2 rounded-md border px-3 py-2 ${
|
||||
className={`border-border bg-background/30 flex min-h-12 flex-wrap items-center gap-2 rounded-md border px-3 py-2 ${
|
||||
isDragging ? "ring-primary z-10 opacity-90 shadow-lg ring-2" : ""
|
||||
} ${isHidden ? "opacity-60" : ""}`}
|
||||
>
|
||||
@@ -523,7 +523,7 @@ function SortableTagRow({
|
||||
className="text-muted-foreground h-4 w-4 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="min-w-0 flex-1 truncate text-sm" title={name}>
|
||||
<span className="min-w-[8rem] flex-1 truncate text-sm" title={name}>
|
||||
{name}
|
||||
</span>
|
||||
{tag.kind === "RATED" && (
|
||||
@@ -536,106 +536,110 @@ function SortableTagRow({
|
||||
{t("mood.manage.usageCount", { count: String(tag.usageCount) })}
|
||||
</Badge>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 sm:size-9"
|
||||
onClick={() => onToggleVisibility(tag)}
|
||||
disabled={disabled}
|
||||
aria-pressed={tag.custom ? undefined : isHidden}
|
||||
aria-label={`${
|
||||
tag.custom
|
||||
? t("mood.manage.archive")
|
||||
: isHidden
|
||||
? t("mood.manage.showTag")
|
||||
: t("mood.manage.hideTag")
|
||||
} — ${name}`}
|
||||
title={
|
||||
tag.custom
|
||||
? t("mood.manage.archive")
|
||||
: isHidden
|
||||
? t("mood.manage.showTag")
|
||||
: t("mood.manage.hideTag")
|
||||
}
|
||||
>
|
||||
{tag.custom ? (
|
||||
<Archive className="h-4 w-4" aria-hidden="true" />
|
||||
) : isHidden ? (
|
||||
<EyeOff className="h-4 w-4" aria-hidden="true" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" aria-hidden="true" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 sm:size-9"
|
||||
onClick={() => onMove(groupKey, tag.key, -1)}
|
||||
disabled={index === 0 || disabled}
|
||||
aria-label={`${t("mood.manage.moveUp")} — ${name}`}
|
||||
>
|
||||
<ArrowUp className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 sm:size-9"
|
||||
onClick={() => onMove(groupKey, tag.key, 1)}
|
||||
disabled={index === total - 1 || disabled}
|
||||
aria-label={`${t("mood.manage.moveDown")} — ${name}`}
|
||||
>
|
||||
<ArrowDown className="h-4 w-4" />
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 sm:size-9"
|
||||
aria-label={`${t("common.moreOptions")} — ${name}`}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{tag.custom && (
|
||||
<DropdownMenuItem onClick={() => onEdit(tag)}>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
{t("common.edit")}
|
||||
</DropdownMenuItem>
|
||||
{/* The action cluster wraps to its own line on a narrow row so the
|
||||
tag name keeps a readable floor instead of truncating away. */}
|
||||
<div className="ml-auto flex shrink-0 items-center gap-1">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 sm:size-9"
|
||||
onClick={() => onToggleVisibility(tag)}
|
||||
disabled={disabled}
|
||||
aria-pressed={tag.custom ? undefined : isHidden}
|
||||
aria-label={`${
|
||||
tag.custom
|
||||
? t("mood.manage.archive")
|
||||
: isHidden
|
||||
? t("mood.manage.showTag")
|
||||
: t("mood.manage.hideTag")
|
||||
} — ${name}`}
|
||||
title={
|
||||
tag.custom
|
||||
? t("mood.manage.archive")
|
||||
: isHidden
|
||||
? t("mood.manage.showTag")
|
||||
: t("mood.manage.hideTag")
|
||||
}
|
||||
>
|
||||
{tag.custom ? (
|
||||
<Archive className="h-4 w-4" aria-hidden="true" />
|
||||
) : isHidden ? (
|
||||
<EyeOff className="h-4 w-4" aria-hidden="true" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" aria-hidden="true" />
|
||||
)}
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<FolderInput className="mr-2 h-4 w-4" />
|
||||
{t("mood.manage.moveToGroup")}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
{groupOptions
|
||||
.filter((group) => group.key !== groupKey)
|
||||
.map((group) => (
|
||||
<DropdownMenuItem
|
||||
key={group.key}
|
||||
onClick={() => onMoveToGroup(tag, group.key)}
|
||||
>
|
||||
{group.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
{tag.custom && (
|
||||
<DropdownMenuItem onClick={() => onToggleVisibility(tag)}>
|
||||
<Archive className="mr-2 h-4 w-4" />
|
||||
{t("mood.manage.archive")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 sm:size-9"
|
||||
onClick={() => onMove(groupKey, tag.key, -1)}
|
||||
disabled={index === 0 || disabled}
|
||||
aria-label={`${t("mood.manage.moveUp")} — ${name}`}
|
||||
>
|
||||
<ArrowUp className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 sm:size-9"
|
||||
onClick={() => onMove(groupKey, tag.key, 1)}
|
||||
disabled={index === total - 1 || disabled}
|
||||
aria-label={`${t("mood.manage.moveDown")} — ${name}`}
|
||||
>
|
||||
<ArrowDown className="h-4 w-4" />
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 sm:size-9"
|
||||
aria-label={`${t("common.moreOptions")} — ${name}`}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{tag.custom && (
|
||||
<DropdownMenuItem onClick={() => onEdit(tag)}>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
{t("common.edit")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<FolderInput className="mr-2 h-4 w-4" />
|
||||
{t("mood.manage.moveToGroup")}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
{groupOptions
|
||||
.filter((group) => group.key !== groupKey)
|
||||
.map((group) => (
|
||||
<DropdownMenuItem
|
||||
key={group.key}
|
||||
onClick={() => onMoveToGroup(tag, group.key)}
|
||||
>
|
||||
{group.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
{tag.custom && (
|
||||
<DropdownMenuItem onClick={() => onToggleVisibility(tag)}>
|
||||
<Archive className="mr-2 h-4 w-4" />
|
||||
{t("mood.manage.archive")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -180,7 +180,7 @@ export function ResponsiveSheet({
|
||||
{footer ? (
|
||||
<SheetFooter
|
||||
data-slot="responsive-sheet-footer"
|
||||
className="border-border/70 bg-background/95 supports-[backdrop-filter]:bg-background/80 sticky bottom-0 mt-0 flex-row justify-end gap-2 border-t p-4 backdrop-blur"
|
||||
className="border-border/70 bg-background/95 supports-[backdrop-filter]:bg-background/80 sticky bottom-0 mt-0 flex-row justify-end gap-2 border-t p-4 pb-[calc(env(safe-area-inset-bottom,0px)+1rem)] backdrop-blur"
|
||||
>
|
||||
{footer}
|
||||
</SheetFooter>
|
||||
|
||||
Reference in new issue
Block a user