mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
fix(charts): keep axis unit labels on one line; 44 px date and time triggers
Recharts word-wraps a tick label that it measures wider than its axis, and it measures in the page's body font rather than the tick's 11 px. "62 bpm" on the pulse chart under a workout and "106 mg/dL" on the glucose page therefore broke onto two lines at every width, and the pulse chart's second line sat on the first x tick. Axis units are now joined with a no-break space through `axisUnitSuffix`, so the label is one word and stays on one line. A guard fails when a chart axis joins a unit with a plain space. The calendar and clock buttons in the date and time fields paint at 32 px; a `::before` halo now gives them a 44 px touch target without changing the field.
This commit is contained in:
1 parent
801295f55a
commit
debd44a75b
9 files changed
+109
-6
No files matched your search
@@ -71,6 +71,7 @@ import {
|
||||
type MetricWindowStats,
|
||||
} from "@/lib/charts/window-stats";
|
||||
import { shouldFireDataReady } from "@/lib/charts/data-ready-latch";
|
||||
import { axisUnitSuffix } from "@/lib/charts/axis-unit";
|
||||
|
||||
// The range tabs select a CALENDAR-DAY window ending now — `days: 7` is
|
||||
// "the last 7 days", not "the last 7 readings". The labels said "points"
|
||||
@@ -1982,8 +1983,8 @@ export function HealthChart({
|
||||
: String(value)
|
||||
}
|
||||
unit={
|
||||
showYAxisUnit && (yAxisUnit ?? unit)
|
||||
? ` ${yAxisUnit ?? unit}`
|
||||
showYAxisUnit
|
||||
? axisUnitSuffix(yAxisUnit ?? unit)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -24,6 +24,7 @@ import { TileHeader } from "@/components/insights/tile-header";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { axisUnitSuffix } from "@/lib/charts/axis-unit";
|
||||
import {
|
||||
BUCKET_MINUTES,
|
||||
type IntradayHrBucket,
|
||||
@@ -300,7 +301,7 @@ export function IntradayPulseChart({
|
||||
tick={{ fontSize: 11, fill: "var(--muted-foreground)" }}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
unit=" bpm"
|
||||
unit={axisUnitSuffix("bpm")}
|
||||
width={52}
|
||||
/>
|
||||
<Tooltip
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from "recharts";
|
||||
import { makeFormatters } from "@/lib/format-locale";
|
||||
import { useDateFormatPreference, useTranslations } from "@/lib/i18n/context";
|
||||
import { axisUnitSuffix } from "@/lib/charts/axis-unit";
|
||||
|
||||
export interface EfficacyChartTarget {
|
||||
label: string;
|
||||
@@ -169,7 +170,7 @@ export function EfficacyChart({
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
width={40}
|
||||
unit={unitSuffix ? ` ${unitSuffix}` : undefined}
|
||||
unit={axisUnitSuffix(unitSuffix)}
|
||||
/>
|
||||
<YAxis
|
||||
yAxisId="adherence"
|
||||
|
||||
@@ -93,6 +93,19 @@ describe("<DateField>", () => {
|
||||
expect(html).toContain("sm:h-10");
|
||||
});
|
||||
|
||||
it("gives the calendar button a 44 px touch target at its 32 px size", () => {
|
||||
const html = render(<DateField value="2026-02-19" />);
|
||||
const button = /<button[^>]*aria-label="Open date picker"[^>]*>/.exec(
|
||||
html,
|
||||
)?.[0];
|
||||
expect(button).toBeDefined();
|
||||
// Painted size unchanged; the ::before halo adds 6 px per edge.
|
||||
expect(button).toContain("h-8 w-8");
|
||||
expect(button).toContain("relative");
|
||||
expect(button).toContain("before:-inset-1.5");
|
||||
expect(button).toContain("before:absolute");
|
||||
});
|
||||
|
||||
it("exposes a labelled calendar picker affordance", () => {
|
||||
const html = render(<DateField value="2026-02-19" />);
|
||||
expect(html).toContain('aria-label="Open date picker"');
|
||||
|
||||
@@ -105,6 +105,18 @@ describe("formatHhmm", () => {
|
||||
});
|
||||
|
||||
describe("<TimeField> SSR", () => {
|
||||
it("gives the clock button a 44 px touch target at its 32 px size", () => {
|
||||
const html = render(<TimeField value="14:05" />, "de");
|
||||
const button = /<button[^>]*data-slot="time-field-trigger"[^>]*>/.exec(
|
||||
html,
|
||||
)?.[0];
|
||||
expect(button).toBeDefined();
|
||||
expect(button).toContain("h-8 w-8");
|
||||
expect(button).toContain("relative");
|
||||
expect(button).toContain("before:-inset-1.5");
|
||||
expect(button).toContain("before:absolute");
|
||||
});
|
||||
|
||||
it("paints the value in 24h under the de AUTO locale and exposes the picker affordance", () => {
|
||||
const html = render(<TimeField value="14:05" />, "de");
|
||||
expect(html).toContain('value="14:05"');
|
||||
|
||||
@@ -225,7 +225,9 @@ export const DateField = React.forwardRef<HTMLInputElement, DateFieldProps>(
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
aria-label={t("common.openDatePicker")}
|
||||
className="text-muted-foreground hover:text-foreground flex h-8 w-8 shrink-0 items-center justify-center rounded-md disabled:cursor-not-allowed disabled:opacity-50"
|
||||
// The button paints at 32 px; the `::before` halo (6 px per edge)
|
||||
// makes the touch target 44 px without changing the field.
|
||||
className="text-muted-foreground hover:text-foreground relative flex h-8 w-8 shrink-0 items-center justify-center rounded-md before:absolute before:-inset-1.5 before:content-[''] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<CalendarIcon className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
@@ -189,7 +189,9 @@ export const TimeField = React.forwardRef<HTMLInputElement, TimeFieldProps>(
|
||||
tabIndex={-1}
|
||||
disabled={disabled}
|
||||
aria-label={t("common.openTimePicker")}
|
||||
className="text-muted-foreground hover:text-foreground flex h-8 w-8 shrink-0 items-center justify-center rounded-md disabled:cursor-not-allowed disabled:opacity-50"
|
||||
// The button paints at 32 px; the `::before` halo (6 px per edge)
|
||||
// makes the touch target 44 px without changing the field.
|
||||
className="text-muted-foreground hover:text-foreground relative flex h-8 w-8 shrink-0 items-center justify-center rounded-md before:absolute before:-inset-1.5 before:content-[''] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
data-slot="time-field-trigger"
|
||||
>
|
||||
<Clock className="h-4 w-4" aria-hidden="true" />
|
||||
|
||||
Reference in new issue
Block a user