fix: T4a browser check findings
- Withdrawal wording from one backend helper everywhere ("bis
einschließlich TT.MM. – ab TT.MM. wieder erntefähig", year when it
crosses New Year); withdrawal-status returns lastBlockedDay and the
ready text, the frontend no longer computes dates
- Calendar events name colony or apiary, withdrawal events carry
product and detail; compact withdrawal rows on mobile
- One bottling warning per colony and treatment; pre-save hint
- Offline: online-only actions disabled with a reason, cached data
shows its timestamp, open sheets block submit offline
- Mobile: no horizontal scroll at 360 px, touch targets >= 44 px,
toasts and the add button no longer cover content
- Cleanup sheet without destructive preselection; natural sort of
colony codes; collapsible automation rule cards; varroa list newest
first with chart axes; text fixes
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
32c61c8e14
commit
c2f71bcb03
101 files changed
+2189
-271
No files matched your search
@@ -0,0 +1,65 @@
|
||||
import { afterEach, beforeAll, describe, expect, it } from "vitest";
|
||||
import { act, render, screen } from "@testing-library/react";
|
||||
import { toast } from "sonner";
|
||||
import { Toaster } from "./sonner";
|
||||
|
||||
beforeAll(() => {
|
||||
// jsdom has no matchMedia; Sonner's Toaster asks for the color scheme.
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
writable: true,
|
||||
value: (query: string) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addEventListener: () => undefined,
|
||||
removeEventListener: () => undefined,
|
||||
addListener: () => undefined,
|
||||
removeListener: () => undefined,
|
||||
dispatchEvent: () => false,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
toast.dismiss();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Toaster", () => {
|
||||
it("sits at the bottom with a phone offset above the bottom nav and the quick-add button", async () => {
|
||||
const { container } = render(<Toaster />);
|
||||
act(() => {
|
||||
toast("Verworfen");
|
||||
});
|
||||
|
||||
await screen.findByText("Verworfen");
|
||||
const list = container.ownerDocument.querySelector("[data-sonner-toaster]") as HTMLElement;
|
||||
expect(list).toHaveAttribute("data-y-position", "bottom");
|
||||
// One logic for both breakpoints: the CSS variable follows the app's sm breakpoint (640 px).
|
||||
expect(list.style.getPropertyValue("--mobile-offset-bottom")).toBe("var(--toast-offset-bottom)");
|
||||
expect(list.style.getPropertyValue("--offset-bottom")).toBe("var(--toast-offset-bottom)");
|
||||
});
|
||||
|
||||
it("makes the action button of every toast 44 px high", async () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
toast("Verworfen", { action: { label: "Rückgängig", onClick: () => undefined } });
|
||||
});
|
||||
|
||||
const action = await screen.findByRole("button", { name: "Rückgängig" });
|
||||
expect(action.className).toContain("h-11!");
|
||||
expect(action.className).toContain("min-w-11!");
|
||||
});
|
||||
|
||||
it("makes the close button a 44 px target", async () => {
|
||||
render(<Toaster closeButton />);
|
||||
act(() => {
|
||||
toast("Hinweis");
|
||||
});
|
||||
|
||||
await screen.findByText("Hinweis");
|
||||
const close = document.querySelector("[data-close-button]") as HTMLElement;
|
||||
expect(close.className).toContain("size-11!");
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,13 @@ import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
||||
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
|
||||
|
||||
// Toasts sit at the bottom: at the top they cover the page heading and the
|
||||
// notification bell. One offset for every width: --toast-offset-bottom
|
||||
// (globals.css) clears the bottom nav and the quick-add button below the app's
|
||||
// sm breakpoint (640 px) and the sticky save bar everywhere. Sonner switches to
|
||||
// its phone layout at 600 px, so both offsets take the same variable.
|
||||
const TOAST_OFFSET = { bottom: "var(--toast-offset-bottom)", left: 16, right: 16 };
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
|
||||
@@ -11,6 +18,9 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
position="bottom-right"
|
||||
offset={TOAST_OFFSET}
|
||||
mobileOffset={TOAST_OFFSET}
|
||||
icons={{
|
||||
success: (
|
||||
<CircleCheckIcon className="size-4" />
|
||||
@@ -39,6 +49,10 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast: "cn-toast",
|
||||
// Sonner's own buttons are 24 px high; a tap target needs 44 px.
|
||||
actionButton: "h-11! min-h-11! min-w-11! px-4!",
|
||||
closeButton: "size-11!",
|
||||
cancelButton: "h-11! min-h-11! min-w-11! px-4!",
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
|
||||
Reference in new issue
Block a user