mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
Give every empty list one empty state and one add action
EmptyState now has a single action shape: centred at its own width on the 44 px tap floor, never stretched edge to edge on some pages and compact on others; the per-call-site size switch is gone, and the dashed card takes the card radius. While a list is empty its empty state carries the add action, so the header drops its own add button there (measurements, mood, medications, labs, vaccinations, documents, illness, check-ups, visits, procedures). The dashboard medication tile of an account without medications offers 'Add a medication' instead of 'no data in the chosen range'.
This commit is contained in:
1 parent
793c19e179
commit
005cb32ea8
30 files changed
+158
-65
No files matched your search
@@ -86,7 +86,6 @@ export default function InsightsBmiPage() {
|
||||
icon={<Ruler className="size-6" />}
|
||||
title={t("insights.bmiEmptyTitle")}
|
||||
description={t("insights.bmiEmptyDescription")}
|
||||
ctaSize="lg"
|
||||
action={
|
||||
<Button size="sm" asChild>
|
||||
<Link href="/settings/account">
|
||||
|
||||
@@ -199,7 +199,6 @@ export default function InsightsMedikamentePage() {
|
||||
icon={<Pill className="size-6" />}
|
||||
title={t("insights.emptyState.medication.title")}
|
||||
description={t("insights.emptyState.medication.description")}
|
||||
ctaSize="lg"
|
||||
action={
|
||||
<Button size="sm" asChild>
|
||||
<Link href="/medications">
|
||||
|
||||
@@ -408,7 +408,6 @@ export default function InsightsPageClient() {
|
||||
icon={<TrendingUp className="size-6" />}
|
||||
title={t("insights.emptyTitle")}
|
||||
description={t("insights.emptyDescription")}
|
||||
ctaSize="lg"
|
||||
action={
|
||||
<Button size="sm" asChild>
|
||||
<Link href="/measurements">
|
||||
@@ -624,7 +623,6 @@ export default function InsightsPageClient() {
|
||||
icon={<SlidersHorizontal className="size-6" />}
|
||||
title={t("insights.editMode.emptyTitle")}
|
||||
description={t("insights.editMode.emptyDescription")}
|
||||
ctaSize="lg"
|
||||
action={
|
||||
<Button size="sm" asChild>
|
||||
<Link href="/settings/layout/insights">
|
||||
|
||||
@@ -58,6 +58,7 @@ export default function LabsPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslations();
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [listEmpty, setListEmpty] = useState(false);
|
||||
// Sticky-footer slot element for the add-result sheet (filled by the
|
||||
// ResponsiveSheet `footer` ref so the form portals its action row there).
|
||||
const [addFooterEl, setAddFooterEl] = useState<HTMLDivElement | null>(null);
|
||||
@@ -185,7 +186,9 @@ export default function LabsPage() {
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : canAddLab ? (
|
||||
) : canAddLab && !listEmpty ? (
|
||||
// Hidden while the list is empty: its empty state carries the
|
||||
// manual add then. The scan choice above has no such twin.
|
||||
<Button
|
||||
onClick={() => setDialogOpen(true)}
|
||||
className="min-h-11 sm:min-h-9"
|
||||
@@ -263,7 +266,10 @@ export default function LabsPage() {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<LabList onAddFirst={() => setDialogOpen(true)} />
|
||||
<LabList
|
||||
onAddFirst={() => setDialogOpen(true)}
|
||||
onEmptyChange={setListEmpty}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -67,6 +67,9 @@ export default function MeasurementsPage() {
|
||||
? `${addParam}\u0000${returnToParam ?? ""}`
|
||||
: null;
|
||||
const [dialogOpen, setDialogOpen] = useState(() => initialAdd != null);
|
||||
// The list's empty state carries the add action while there is nothing
|
||||
// to list, so the header drops its own then.
|
||||
const [listEmpty, setListEmpty] = useState(false);
|
||||
const [defaultType, setDefaultType] = useState<string | undefined>(
|
||||
() => initialAdd ?? undefined,
|
||||
);
|
||||
@@ -151,7 +154,7 @@ export default function MeasurementsPage() {
|
||||
// admits, so this is the header action a delegate keeps. A read-only
|
||||
// delegate loses it entirely rather than meeting a disabled one.
|
||||
actions={
|
||||
canAddMeasurement ? (
|
||||
canAddMeasurement && !listEmpty ? (
|
||||
<Button
|
||||
data-slot="measurement-add"
|
||||
className="min-h-11 sm:min-h-9"
|
||||
@@ -212,6 +215,7 @@ export default function MeasurementsPage() {
|
||||
fallback={<Skeleton className="h-[28rem] w-full rounded-xl" />}
|
||||
>
|
||||
<MeasurementList
|
||||
onEmptyChange={setListEmpty}
|
||||
onAddFirst={() => {
|
||||
setReturnTo(null);
|
||||
setDialogOpen(true);
|
||||
|
||||
@@ -521,6 +521,8 @@ export default function MedicationsPageClient() {
|
||||
),
|
||||
});
|
||||
|
||||
const listEmpty = !isLoading && !isError && !medications?.length;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PullToRefreshIndicator {...pull} />
|
||||
@@ -575,7 +577,9 @@ export default function MedicationsPageClient() {
|
||||
button's responsive tap-target floor (`min-h-11 sm:min-h-9`) so
|
||||
both primary "add" entry points clear the WCAG 2.5.5 44px mobile
|
||||
minimum identically. */}
|
||||
{canAddMedication && (
|
||||
{/* While the list is empty its empty state carries the one
|
||||
add action; a second one in the header said it twice. */}
|
||||
{canAddMedication && !listEmpty && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="min-h-11 sm:min-h-9">
|
||||
|
||||
@@ -28,6 +28,9 @@ export default function MoodPageClient() {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
// The list's empty state carries the add action while there is nothing
|
||||
// to list, so the header drops its own then.
|
||||
const [listEmpty, setListEmpty] = useState(false);
|
||||
// v1.4.27 R4 RC2 — DOM-ref handle the form portals its action row
|
||||
// into. The ref lives on the `<ResponsiveSheet>` footer slot so the
|
||||
// Sheet branch can sticky-pin Save / Cancel above the keyboard.
|
||||
@@ -109,7 +112,7 @@ export default function MoodPageClient() {
|
||||
)}
|
||||
{/* A mood entry is a MANAGE create: a WRITE delegate gets no add
|
||||
path here, a guardian or a MANAGE delegate does. */}
|
||||
{canAddMood && (
|
||||
{canAddMood && !listEmpty && (
|
||||
<Button
|
||||
onClick={() => setDialogOpen(true)}
|
||||
className="min-h-11 sm:min-h-9"
|
||||
@@ -136,7 +139,10 @@ export default function MoodPageClient() {
|
||||
/>
|
||||
</ResponsiveSheet>
|
||||
|
||||
<MoodList onAddFirst={() => setDialogOpen(true)} />
|
||||
<MoodList
|
||||
onAddFirst={() => setDialogOpen(true)}
|
||||
onEmptyChange={setListEmpty}
|
||||
/>
|
||||
|
||||
{/* 2026-07-17 UX/IA audit M9 — mood tracking, the mental-wellbeing
|
||||
screeners, and the mood insights page form one mental-health
|
||||
|
||||
Reference in new issue
Block a user