Merge pull request #39108 from yatishgoel/bugfix/form-field-checkbox-width

fix(ui): stop checkboxes stretching to the full width of a form field
This commit is contained in:
ryan-crabbe-berri 2026-09-01 11:03:36 -07:00 committed by GitHub
commit 720c1fa28c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 84 additions and 3 deletions

View file

@ -5,6 +5,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import { CreateUserButton } from "./CreateUserButton";
import * as networking from "./networking";
import { toast } from "@/lib/toast";
import { expectControlBesideLabel } from "../../tests/fieldOrientation";
vi.mock("./networking", () => ({
userCreateCall: vi.fn(),
@ -294,6 +295,20 @@ describe("CreateUserButton", () => {
});
});
it("lays the send invitation email checkbox out beside its label", async () => {
const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never });
renderWithProviders(<CreateUserButton {...defaultProps} />);
await waitFor(() => {
expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument();
});
await user.click(screen.getByRole("button", { name: /\+ invite user/i }));
const dialog = screen.getByRole("dialog", { name: /invite user/i });
expectControlBesideLabel(within(dialog).getByRole("checkbox"));
});
describe("organizations", () => {
it("should send organizations list in POST body when organizations are selected", async () => {
const { useOrganizations } = await import("@/app/(dashboard)/hooks/organizations/useOrganizations");

View file

@ -270,7 +270,7 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
);
const sendInviteEmailField = (
<FormField control={form.control} name="send_invite_email" label="Send invitation email">
<FormField control={form.control} name="send_invite_email" label="Send invitation email" orientation="horizontal">
{({ id, value, onChange, onBlur }) => (
<Checkbox id={id} checked={value} onCheckedChange={onChange} onBlur={onBlur} />
)}

View file

@ -9,6 +9,7 @@ import BaseSSOSettingsForm, {
submitMountedSSOValues,
useSSOSettingsForm,
} from "./BaseSSOSettingsForm";
import { expectControlBesideLabel } from "../../../../../../tests/fieldOrientation";
const user = () => userEvent.setup({ pointerEventsCheck: 0 });
@ -233,6 +234,38 @@ describe("BaseSSOSettingsForm", () => {
expect(screen.queryByText("Use Team Mappings")).not.toBeInTheDocument();
});
it("lays a provider checkbox field out beside its label", async () => {
const TestWrapper = () => {
const form = useSSOSettingsForm("sso-settings");
return <BaseSSOSettingsForm form={form} onFormSubmit={vi.fn()} />;
};
renderWithProviders(<TestWrapper />);
await openProviderDropdown();
await user().click(await screen.findByText(/saml sso/i));
expectControlBesideLabel(
await screen.findByRole("checkbox", { name: "Allow IdP-initiated (unsolicited) responses" }),
);
});
it.each(["Use Role Mappings", "Use Team Mappings"])("lays the %s toggle out beside its label", async (label) => {
const TestWrapper = () => {
const form = useSSOSettingsForm("sso-settings");
return <BaseSSOSettingsForm form={form} onFormSubmit={vi.fn()} />;
};
renderWithProviders(<TestWrapper />);
await openProviderDropdown();
await user().click(await screen.findByText(/okta/i));
expectControlBesideLabel(await screen.findByRole("checkbox", { name: label }));
});
});
describe("renderProviderFields", () => {

View file

@ -303,7 +303,7 @@ const SSOProviderField = ({ field }: { field: SSOProviderConfig["fields"][number
if (field.type === "checkbox") {
return (
<FormField control={control} name={field.name} label={field.label}>
<FormField control={control} name={field.name} label={field.label} orientation="horizontal">
{({ value, onChange, onBlur, id, ...rest }) => (
<Checkbox
id={id}
@ -413,7 +413,7 @@ export const MappingToggleField = ({
const { control } = useFormContext<SSOSettingsFormValues>();
return (
<FormField control={control} name={name} label={label}>
<FormField control={control} name={name} label={label} orientation="horizontal">
{({ value, onChange, onBlur, id, ...rest }) => (
<Checkbox
id={id}

View file

@ -14,6 +14,7 @@ import {
FieldSet,
FieldTitle,
} from "./field";
import { ROW_LAYOUT_CLASSES, STRETCH_CHILDREN_CLASS } from "../../../tests/fieldOrientation";
describe("FieldError", () => {
it("renders nothing when there are no errors and no children", () => {
@ -85,6 +86,20 @@ describe("Field", () => {
expect(screen.getByRole("group")).toHaveAttribute("data-orientation", "horizontal");
});
it("stretches every child when vertical, which is what inputs, selects and textareas want", () => {
render(<Field />);
expect(screen.getByRole("group")).toHaveClass("flex-col", STRETCH_CHILDREN_CLASS);
});
it("lays children in a row at their own width when horizontal, so a checkbox stays square", () => {
render(<Field orientation="horizontal" />);
const field = screen.getByRole("group");
expect(field).toHaveClass(...ROW_LAYOUT_CLASSES);
expect(field).not.toHaveClass(STRETCH_CHILDREN_CLASS);
});
});
describe("field primitives forward refs to their DOM node", () => {

View file

@ -0,0 +1,18 @@
import { expect } from "vitest";
export const ROW_LAYOUT_CLASSES = ["flex-row", "items-center"] as const;
export const STRETCH_CHILDREN_CLASS = "*:w-full";
/**
* Asserts a control sits beside its label at its own width instead of being stretched across the
* field. Reaches for the resolved classes because the defect is purely visual: nothing accessible
* distinguishes a square checkbox from a full-width bar.
*/
export const expectControlBesideLabel = (control: HTMLElement): void => {
const field = control.closest('[data-slot="field"]');
if (field === null) throw new Error("control is not rendered inside a form field");
expect(field).toHaveAttribute("data-orientation", "horizontal");
expect(field).toHaveClass(...ROW_LAYOUT_CLASSES);
expect(field).not.toHaveClass(STRETCH_CHILDREN_CLASS);
};