Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions libs/react-components/specs/checkbox.browser.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,64 @@ describe("Checkbox", () => {
expect(childValue.element().textContent).toBe("false");
});
});

it("should have a 44px x 44px touch target area", async () => {
const result = render(
<GoabCheckbox testId="test-checkbox" name="test" text="Test Checkbox" />
);

const checkbox = result.getByTestId("test-checkbox");
await vi.waitFor(() => {
expect(checkbox.element()).toBeTruthy();
});

const container = checkbox.element().querySelector(".container") as HTMLElement;
expect(container).toBeTruthy();

// Get computed styles for the ::before pseudo-element (touch target)
const beforeStyles = window.getComputedStyle(container, "::before");

// Verify the touch target dimensions
expect(beforeStyles.width).toBe("44px");
expect(beforeStyles.height).toBe("44px");
expect(beforeStyles.position).toBe("absolute");

// Verify the container itself has position: relative for proper positioning context
const containerStyles = window.getComputedStyle(container);
expect(containerStyles.position).toBe("relative");

// Verify the actual visual size of the container (24px) vs touch target (44px)
const containerRect = container.getBoundingClientRect();
expect(containerRect.width).toBe(24); // Visual checkbox is 24px
expect(containerRect.height).toBe(24); // Visual checkbox is 24px

// Verify the transform is applied correctly for centering
// CSS: transform: translate(-50%, -50%) converts to matrix(a, b, c, d, tx, ty)
// Matrix breakdown:
// - (1, 0, 0, 1) = identity matrix (no scaling/rotation)
// - (-22, -22) = translate by -22px in X and Y directions
// Math: 50% of 44px = 22px, so translate(-50%, -50%) = translate(-22px, -22px)
// Regex explanation:
// - matrix\(1, 0, 0, 1, = identity matrix
// - -2[0-9.]+ = negative number starting with -2 (e.g., -22, -23.6)
// - Flexible pattern accounts for border widths and sub-pixel rendering
expect(beforeStyles.transform).toMatch(/matrix\(1, 0, 0, 1, -2[0-9.]+, -2[0-9.]+\)/);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is extremely specific and I think very brittle. After the tests above, I'm not sure this is adding anything useful, and would make the test far more brittle than I would like.


// Check ::after pseudo-element (should not interfere with touch target)
const afterStyles = window.getComputedStyle(container, "::after");
// ::after should not have conflicting dimensions or positioning
expect(afterStyles.position).not.toBe("absolute");

// Final verification: Check that all styles are applied and rendered
// After the page is fully loaded and all CSS is computed
await vi.waitFor(() => {
const finalContainerStyles = window.getComputedStyle(container);
const finalBeforeStyles = window.getComputedStyle(container, "::before");

// Verify final computed styles match expectations
expect(finalContainerStyles.position).toBe("relative");
expect(finalBeforeStyles.width).toBe("44px");
expect(finalBeforeStyles.height).toBe("44px");
});
});
});
63 changes: 63 additions & 0 deletions libs/react-components/specs/radio.browser.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,67 @@ describe("Radio", () => {
expect(selectedValue.element().textContent).toBe("apple");
});
});

it("should have a 44px x 44px touch target area", async () => {
const result = render(
<GoabRadioGroup name="test" value="">
<GoabRadioItem name="test" value="option1" label="Option 1" />
</GoabRadioGroup>
);

const radioInput = result.getByTestId("radio-option-option1");
await vi.waitFor(() => {
expect(radioInput.element()).toBeTruthy();
});

// Get the parent label element and find the .icon element
const label = radioInput.element().closest("label");
expect(label).toBeTruthy();

const icon = label?.querySelector(".icon") as HTMLElement;
expect(icon).toBeTruthy();

// Get computed styles for the ::before pseudo-element (touch target)
const beforeStyles = window.getComputedStyle(icon, "::before");

// Verify the touch target dimensions
expect(beforeStyles.width).toBe("44px");
expect(beforeStyles.height).toBe("44px");
expect(beforeStyles.position).toBe("absolute");

// Verify the icon itself has position: relative for proper positioning context
const iconStyles = window.getComputedStyle(icon);
expect(iconStyles.position).toBe("relative");

// Verify the actual visual size of the icon (24px) vs touch target (44px)
const iconRect = icon.getBoundingClientRect();
expect(iconRect.width).toBe(24); // Visual icon is 24px
expect(iconRect.height).toBe(24); // Visual icon is 24px

// Verify the transform is applied correctly for centering
// CSS: transform: translate(-50%, -50%) converts to matrix(a, b, c, d, tx, ty)
// Matrix breakdown:
// - (1, 0, 0, 1) = identity matrix (no scaling/rotation)
// - (-22, -22) = translate by -22px in X and Y directions
// Math: 50% of 44px = 22px, so translate(-50%, -50%) = translate(-22px, -22px)
// This centers the 44px touch target on the 24px icon
expect(beforeStyles.transform).toBe("matrix(1, 0, 0, 1, -22, -22)");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as the one I made for the Checkbox tests


// Check ::after pseudo-element (should not interfere with touch target)
const afterStyles = window.getComputedStyle(icon, "::after");
// ::after should not have conflicting dimensions or positioning
expect(afterStyles.position).not.toBe("absolute");

// Final verification: Check that all styles are applied and rendered
// After the page is fully loaded and all CSS is computed
await vi.waitFor(() => {
const finalIconStyles = window.getComputedStyle(icon);
const finalBeforeStyles = window.getComputedStyle(icon, "::before");

// Verify final computed styles match expectations
expect(finalIconStyles.position).toBe("relative");
expect(finalBeforeStyles.width).toBe("44px");
expect(finalBeforeStyles.height).toBe("44px");
});
});
});
24 changes: 18 additions & 6 deletions libs/web-components/src/components/checkbox/Checkbox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,12 @@
const checkboxEl = (_rootEl.getRootNode() as ShadowRoot)?.host as HTMLElement;
const fromCheckboxList = checkboxEl?.closest("goa-checkbox-list") !== null;

relay<FormFieldMountRelayDetail>(
_rootEl,
FormFieldMountMsg,
{ name, el: _rootEl },
{ bubbles: !fromCheckboxList, timeout: 10 },
);
relay<FormFieldMountRelayDetail>(
_rootEl,
FormFieldMountMsg,
{ name, el: _rootEl },
{ bubbles: !fromCheckboxList, timeout: 10 },
);
}

function onChange(e: Event) {
Expand Down Expand Up @@ -387,6 +387,7 @@ max-width: ${maxwidth};

/* Container */
.container {
position: relative;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only with Safari, when I am having a checkbox, and when I click on it, there is a light "shift" 22px to 24px around the checkbox, like the below video. Safari I am testing both 17.4.1 and 18.4 (browserstack)

checkbox-safari.mov

box-sizing: border-box;
border: var(--goa-checkbox-border);
border-radius: var(--goa-checkbox-border-radius);
Expand All @@ -398,6 +399,17 @@ max-width: ${maxwidth};
justify-content: center;
flex: 0 0 auto; /* prevent squishing of checkbox */
}

.container::before {
content: '';
position: absolute;
width: 44px;
height: 44px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

.container:hover {
border: var(--goa-checkbox-border-hover);
}
Expand Down
11 changes: 11 additions & 0 deletions libs/web-components/src/components/radio-item/RadioItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@
}

.icon {
position: relative;
display: inline-block;
height: var(--goa-radio-size);
width: var(--goa-radio-size);
Expand All @@ -354,6 +355,16 @@
margin-top: var(--font-valign-fix);
}

.icon::before {
content: '';
position: absolute;
width: 44px;
height: 44px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

.radio--disabled .label,
.radio--disabled ~ .description {
color: var(--goa-radio-label-color-disabled);
Expand Down