fix(desktop): macos quit flash, first click and press feedback
This commit is contained in:
5 files changed
+91
-11
No files matched your search
@@ -0,0 +1,43 @@
|
||||
// Source-contract guard for shared button press feedback.
|
||||
|
||||
// Overview: desktop click responsiveness depends on buttons giving immediate
|
||||
// visual feedback while pressed. DOM `click` only fires on pointer-up, so a
|
||||
// press with zero visual delta reads as "the click did nothing" and users
|
||||
// retry or hold the button (~1s holds measured in click-probe sessions).
|
||||
//
|
||||
// Two guarantees keep that regression from re-appearing:
|
||||
// 1. Every button variant styles an `active:` pressed state, so pressing
|
||||
// always changes something on screen before pointer-up.
|
||||
// 2. The pressed state must not move or scale the button (`scale-*` /
|
||||
// `translate-*`). A moving hit target can land pointer-up outside the
|
||||
// element it was pressed on, which genuinely drops the click.
|
||||
// Keep these assertions updated alongside the variant class strings.
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const buttonVariantsSource = readFileSync(new URL("../index.ts", import.meta.url), "utf8");
|
||||
|
||||
const VARIANTS = ["default", "outline", "secondary", "ghost", "destructive", "link"] as const;
|
||||
|
||||
function variantClasses(variant: string): string {
|
||||
const match = buttonVariantsSource.match(new RegExp(`${variant}:\\s*"([^"]+)"`));
|
||||
expect(match, `variant "${variant}" exists in buttonVariants`).toBeTruthy();
|
||||
return match![1];
|
||||
}
|
||||
|
||||
describe("button press feedback guard", () => {
|
||||
it.each(VARIANTS)("styles an active pressed state for the %s variant", (variant) => {
|
||||
expect(variantClasses(variant)).toMatch(/(^|\s)active:/);
|
||||
});
|
||||
|
||||
it.each(VARIANTS)("does not move the hit target while %s is pressed", (variant) => {
|
||||
expect(variantClasses(variant)).not.toMatch(/active:(scale|translate|-translate)-/);
|
||||
});
|
||||
|
||||
it("does not move the hit target from the shared base classes", () => {
|
||||
const base = buttonVariantsSource.match(/cva\(\s*"([^"]+)"/);
|
||||
expect(base).toBeTruthy();
|
||||
expect(base![1]).not.toMatch(/active:(scale|translate|-translate)-/);
|
||||
});
|
||||
});
|
||||
@@ -8,12 +8,12 @@ export const buttonVariants = cva(
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||
outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
||||
ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
||||
destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80 [a]:active:bg-primary/90 active:bg-primary/90",
|
||||
outline: "border-border bg-background hover:bg-muted hover:text-foreground active:bg-accent active:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 active:bg-secondary aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
||||
ghost: "hover:bg-muted hover:text-foreground active:bg-accent active:text-accent-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
||||
destructive: "bg-destructive/10 hover:bg-destructive/20 active:bg-destructive/30 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30 dark:active:bg-destructive/40",
|
||||
link: "text-primary underline-offset-4 hover:underline active:opacity-80",
|
||||
},
|
||||
size: {
|
||||
default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||
|
||||
Reference in new issue
Block a user