(LabelledBox) add classic variant support

- Add variant prop to LabelledBox component
- Ignore labelAsPlaceholder in classic mode
- Add CSS styles for .labelled-box--classic
- Add unit tests for both variants

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Nathan Panchout
2026-01-26 20:07:30 +01:00
parent 5b522e34f3
commit 367958ca17
3 changed files with 158 additions and 1 deletions

View File

@@ -77,4 +77,30 @@
}
}
}
&--classic {
label {
position: static;
margin-bottom: var(
--c--components--forms-labelledbox--classic-label-margin-bottom
);
font-size: var(
--c--components--forms-labelledbox--classic-label-font-size
);
color: var(--c--components--forms-labelledbox--label-color--small);
transition: none;
}
.labelled-box__children {
padding-top: 0;
}
&.labelled-box--disabled {
label {
color: var(
--c--components--forms-labelledbox--label-color--small--disabled
);
}
}
}
}

View File

@@ -0,0 +1,124 @@
import { render, screen } from "@testing-library/react";
import React from "react";
import { expect } from "vitest";
import { LabelledBox } from "./index";
describe("<LabelledBox/>", () => {
describe("floating variant (default)", () => {
it("renders with floating variant by default", () => {
render(
<LabelledBox label="Test Label">
<input type="text" />
</LabelledBox>,
);
const container = document.querySelector(".labelled-box");
expect(container).not.toHaveClass("labelled-box--classic");
});
it("applies placeholder class when labelAsPlaceholder is true", () => {
render(
<LabelledBox label="Test Label" labelAsPlaceholder={true}>
<input type="text" />
</LabelledBox>,
);
const label = screen.getByText("Test Label");
expect(label.closest("label")).toHaveClass("placeholder");
});
it("does not apply placeholder class when labelAsPlaceholder is false", () => {
render(
<LabelledBox label="Test Label" labelAsPlaceholder={false}>
<input type="text" />
</LabelledBox>,
);
const label = screen.getByText("Test Label");
expect(label.closest("label")).not.toHaveClass("placeholder");
});
});
describe("classic variant", () => {
it("renders with classic variant class", () => {
render(
<LabelledBox label="Test Label" variant="classic">
<input type="text" />
</LabelledBox>,
);
const container = document.querySelector(".labelled-box");
expect(container).toHaveClass("labelled-box--classic");
});
it("ignores labelAsPlaceholder in classic variant", () => {
render(
<LabelledBox
label="Test Label"
variant="classic"
labelAsPlaceholder={true}
>
<input type="text" />
</LabelledBox>,
);
const label = screen.getByText("Test Label");
// In classic variant, placeholder class should NOT be applied even if labelAsPlaceholder is true
expect(label.closest("label")).not.toHaveClass("placeholder");
});
it("label is always static in classic variant regardless of labelAsPlaceholder", () => {
const { rerender } = render(
<LabelledBox
label="Test Label"
variant="classic"
labelAsPlaceholder={false}
>
<input type="text" />
</LabelledBox>,
);
let label = screen.getByText("Test Label");
expect(label.closest("label")).not.toHaveClass("placeholder");
rerender(
<LabelledBox
label="Test Label"
variant="classic"
labelAsPlaceholder={true}
>
<input type="text" />
</LabelledBox>,
);
label = screen.getByText("Test Label");
expect(label.closest("label")).not.toHaveClass("placeholder");
});
});
describe("other props work with both variants", () => {
it("applies disabled class in floating variant", () => {
render(
<LabelledBox label="Test Label" disabled>
<input type="text" />
</LabelledBox>,
);
const container = document.querySelector(".labelled-box");
expect(container).toHaveClass("labelled-box--disabled");
});
it("applies disabled class in classic variant", () => {
render(
<LabelledBox label="Test Label" variant="classic" disabled>
<input type="text" />
</LabelledBox>,
);
const container = document.querySelector(".labelled-box");
expect(container).toHaveClass("labelled-box--classic");
expect(container).toHaveClass("labelled-box--disabled");
});
it("applies hideLabel in classic variant", () => {
render(
<LabelledBox label="Test Label" variant="classic" hideLabel>
<input type="text" />
</LabelledBox>,
);
const label = screen.getByText("Test Label");
expect(label.closest("label")).toHaveClass("c__offscreen");
});
});
});

View File

@@ -1,8 +1,10 @@
import React, { PropsWithChildren } from "react";
import classNames from "classnames";
import type { FieldVariant } from ":/components/Forms/types";
export interface Props extends PropsWithChildren {
label?: string;
variant?: FieldVariant;
labelAsPlaceholder?: boolean;
htmlFor?: string;
labelId?: string;
@@ -14,6 +16,7 @@ export interface Props extends PropsWithChildren {
export const LabelledBox = ({
children,
label,
variant = "floating",
labelAsPlaceholder,
htmlFor,
labelId,
@@ -21,9 +24,12 @@ export const LabelledBox = ({
horizontal,
disabled,
}: Props) => {
const isClassic = variant === "classic";
return (
<div
className={classNames("labelled-box", {
"labelled-box--classic": isClassic,
"labelled-box--no-label": hideLabel,
"labelled-box--horizontal": horizontal,
"labelled-box--disabled": disabled,
@@ -32,7 +38,8 @@ export const LabelledBox = ({
{label && (
<label
className={classNames("labelled-box__label", {
placeholder: labelAsPlaceholder,
// In classic variant, labelAsPlaceholder is ignored (label is always static)
placeholder: !isClassic && labelAsPlaceholder,
c__offscreen: hideLabel,
})}
htmlFor={htmlFor}