♻️(react) make className standard across components

The className prop was sometimes set onto the nested element and
sometimes on the container element, which was not consistent. Now
we always set the className onto the upmost element.
This commit is contained in:
Nathan Vasse
2024-03-04 15:21:09 +01:00
committed by NathanVss
parent f398e51db3
commit 20f5bb703b
23 changed files with 188 additions and 17 deletions

View File

@@ -249,4 +249,11 @@ describe("<Alert/>", () => {
screen.queryByText("Additional information"),
).not.toBeInTheDocument();
});
it("renders with className", async () => {
render(<Alert className="my-custom-class" />);
expect(
document.querySelector(".c__alert.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -87,4 +87,11 @@ describe("<Button/>", () => {
tokens.themes.default.components.button["border-radius"],
).toBeDefined();
});
it("renders with className", async () => {
render(<Button className="my-custom-class" />);
expect(
document.querySelector(".c__button.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -382,6 +382,26 @@ describe("<DataGrid/>", () => {
});
});
it("renders with className", async () => {
render(
<CunninghamProvider>
<DataGrid
columns={[
{
field: "name",
headerName: "Name",
},
]}
rows={[]}
className="my-custom-class"
/>
</CunninghamProvider>,
);
expect(
document.querySelector(".c__datagrid.my-custom-class"),
).toBeInTheDocument();
});
it("should render column with specific width", async () => {
const database = Array.from(Array(10)).map(() => ({
id: faker.string.uuid(),

View File

@@ -64,6 +64,7 @@ export interface BaseProps<T extends Row = Row> {
emptyCta?: ReactNode;
hideEmptyPlaceholderImage?: boolean;
enableSorting?: boolean;
className?: string;
}
export interface DataGridProps<T extends Row = Row> extends BaseProps<T> {
@@ -86,6 +87,7 @@ export const DataGrid = <T extends Row>({
onRowSelectionChange,
rowSelection,
tableOptions,
className,
emptyPlaceholderLabel,
emptyCta,
hideEmptyPlaceholderImage,
@@ -297,7 +299,7 @@ export const DataGrid = <T extends Row>({
return (
<div
className={classNames("c__datagrid", {
className={classNames("c__datagrid", className, {
"c__datagrid--empty": isEmpty,
"c__datagrid--loading": isLoading,
})}

View File

@@ -1,6 +1,7 @@
import userEvent from "@testing-library/user-event";
import React, { useState } from "react";
import { render, screen } from "@testing-library/react";
import { expect } from "vitest";
import {
Checkbox,
CheckboxGroup,
@@ -171,4 +172,18 @@ describe("<Checkbox/>", () => {
expect(input.checked).toEqual(false);
screen.getByText("Checked = false|");
});
it("renders with className", async () => {
render(
<Checkbox label="Agree" checked={true} className="my-custom-class" />,
);
expect(
document.querySelector(".c__checkbox.my-custom-class"),
).toBeInTheDocument();
});
it("renders group with className", async () => {
render(<CheckboxGroup className="my-custom-class" />);
expect(
document.querySelector(".c__checkbox__group.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -22,7 +22,6 @@ const Template: StoryFn<typeof Checkbox> = (args) => (
export const Default = {
render: Template,
args: {},
};
export const Checked = {

View File

@@ -49,7 +49,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
return (
<label
className={classNames("c__checkbox", {
className={classNames("c__checkbox", className, {
"c__checkbox--disabled": props.disabled,
"c__checkbox--full-width": props.fullWidth,
})}
@@ -59,7 +59,6 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
<div className="c__checkbox__wrapper">
<input
type="checkbox"
className={className}
{...inputProps}
onChange={(e) => {
setValue(e.target.checked);
@@ -86,10 +85,11 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
export const CheckboxGroup = ({
children,
className,
...props
}: PropsWithChildren & FieldProps) => {
return (
<Field className="c__checkbox__group" {...props}>
<Field className={classNames("c__checkbox__group", className)} {...props}>
<div className="c__checkbox__group__list">{children}</div>
</Field>
);

View File

@@ -1448,4 +1448,15 @@ describe("<DatePicker/>", () => {
// Make sure value is selected, with the same time as the initial value.
screen.getByText(`Value = 2023-04-12T12:00:00.000Z|`);
});
it("renders with className", async () => {
render(
<CunninghamProvider>
<DatePicker label="Date" className="my-custom-class" />
</CunninghamProvider>,
);
expect(
document.querySelector(".c__field.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -57,6 +57,7 @@ export type DatePickerAuxProps = PropsWithChildren &
const DatePickerAux = forwardRef(
(
{
className,
pickerState,
pickerProps,
onClear,
@@ -86,7 +87,7 @@ const DatePickerAux = forwardRef(
<I18nProvider locale={locale || currentLocale}>
<Field
{...props}
className={classNames({
className={classNames(className, {
"c__date-picker__range__container": isRange,
})}
>

View File

@@ -1175,4 +1175,18 @@ describe("<DateRangePicker/>", () => {
datepickerEnd: "",
});
});
it("renders with className", async () => {
render(
<CunninghamProvider>
<DateRangePicker
startLabel="Start"
endLabel="End"
className="my-custom-class"
/>
</CunninghamProvider>,
);
expect(
document.querySelector(".c__field.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -1,6 +1,7 @@
import { act, render, screen } from "@testing-library/react";
import React, { useRef, useState } from "react";
import userEvent from "@testing-library/user-event";
import { expect } from "vitest";
import { CunninghamProvider } from ":/components/Provider";
import {
FileUploader,
@@ -273,6 +274,17 @@ describe("<FileUploader />", () => {
});
screen.getByText("Value: |");
});
it("renders with className", async () => {
render(
<CunninghamProvider>
<FileUploader className="my-custom-class" />
</CunninghamProvider>,
);
expect(
document.querySelector(".c__field.my-custom-class"),
).toBeInTheDocument();
});
});
describe("Multi", () => {
const expectFiles = (expectedFiles: { name: string; specs: string }[]) => {

View File

@@ -28,7 +28,7 @@ export interface FileUploaderRefType {
export const FileUploader = forwardRef<FileUploaderRefType, FileUploaderProps>(
({ fullWidth, ...props }, ref) => {
return (
<Field fullWidth={fullWidth}>
<Field fullWidth={fullWidth} className={props.className}>
{props.multiple ? (
<FileUploaderMulti {...props} ref={ref} />
) : (

View File

@@ -235,4 +235,11 @@ describe("<Input/>", () => {
render(<Input {...propsInput} />);
expect(spyError).not.toHaveBeenCalled();
});
it("renders with className", async () => {
render(<Input label="First name" className="my-custom-class" />);
expect(
document.querySelector(".c__field.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -39,9 +39,6 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
ref,
) => {
const classes = ["c__input"];
if (className) {
classes.push(className);
}
const inputRef = useRef<HTMLInputElement | null>(null);
const [inputFocus, setInputFocus] = useState(false);
const [value, setValue] = useState(defaultValue || props.value || "");
@@ -82,7 +79,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
} = props;
return (
<Field {...props} rightText={rightTextToUse}>
<Field {...props} rightText={rightTextToUse} className={className}>
{/* We disabled linting for this specific line because we consider that the onClick props is only used for */}
{/* mouse users, so this do not engender any issue for accessibility. */}
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}

View File

@@ -1,6 +1,7 @@
import userEvent from "@testing-library/user-event";
import React from "react";
import { render, screen } from "@testing-library/react";
import { expect } from "vitest";
import {
Radio,
RadioGroup,
@@ -138,4 +139,17 @@ describe("<Radio/>", () => {
render(<Radio {...propsInput} />);
expect(spyError).not.toHaveBeenCalled();
});
it("renders with className", async () => {
render(<Radio label="Agree" checked={true} className="my-custom-class" />);
expect(
document.querySelector(".c__checkbox.my-custom-class"),
).toBeInTheDocument();
});
it("renders group with className", async () => {
render(<RadioGroup className="my-custom-class" />);
expect(
document.querySelector(".c__checkbox__group.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -16,7 +16,7 @@ export type RadioProps = InputHTMLAttributes<HTMLInputElement> &
RadioOnlyProps;
export const Radio = forwardRef<HTMLInputElement, RadioProps>(
({ label, ...props }: RadioProps, ref) => {
({ className, label, ...props }: RadioProps, ref) => {
const {
compact,
fullWidth,
@@ -29,7 +29,7 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
return (
<label
className={classNames("c__checkbox", "c__radio", {
className={classNames("c__checkbox", "c__radio", className, {
"c__checkbox--disabled": props.disabled,
"c__checkbox--full-width": props.fullWidth,
})}
@@ -46,13 +46,14 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
);
export const RadioGroup = ({
className,
children,
style,
...props
}: PropsWithChildren & FieldProps & { style?: React.CSSProperties }) => {
return (
<Field
className="c__radio__group c__checkbox__group"
className={classNames("c__radio__group c__checkbox__group", className)}
compact={true}
{...props}
>

View File

@@ -1976,4 +1976,23 @@ describe("<Select/>", () => {
});
});
});
it("renders with className", async () => {
render(
<CunninghamProvider>
<Select
label="City"
options={[
{
label: "Paris",
},
]}
className="my-custom-class"
/>
</CunninghamProvider>,
);
expect(
document.querySelector(".c__field.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -1871,4 +1871,24 @@ describe("<Select multi={true} />", () => {
});
});
});
it("renders with className", async () => {
render(
<CunninghamProvider>
<Select
label="City"
options={[
{
label: "Paris",
},
]}
multi={true}
className="my-custom-class"
/>
</CunninghamProvider>,
);
expect(
document.querySelector(".c__field.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -156,4 +156,11 @@ describe("<Switch/>", () => {
render(<Switch {...propsInput} />);
expect(spyError).not.toHaveBeenCalled();
});
it("renders with className", async () => {
render(<Switch className="my-custom-class" />);
expect(
document.querySelector(".c__switch.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -15,6 +15,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
({ label, labelSide = "left", ...props }: SwitchProps, ref) => {
const {
compact,
className,
fullWidth,
rightText,
state,
@@ -23,19 +24,22 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
...inputProps
} = props;
const { className: excludeClassName, ...fieldProps } = props;
return (
<label
className={classNames(
"c__checkbox",
"c__switch",
"c__switch--" + labelSide,
className,
{
"c__checkbox--disabled": props.disabled,
"c__switch--full-width": props.fullWidth,
},
)}
>
<Field compact={true} {...props}>
<Field compact={true} {...fieldProps}>
<div className="c__checkbox__container">
{label && <div className="c__checkbox__label">{label}</div>}
<div className="c__switch__rail__wrapper">

View File

@@ -191,4 +191,12 @@ describe("<TextArea/>", () => {
await user.clear(textarea);
screen.getByText("Value: .");
});
it("renders with className", async () => {
render(<TextArea className="my-custom-class" />);
screen.debug();
expect(
document.querySelector(".c__field--textarea.my-custom-class"),
).toBeInTheDocument();
});
});

View File

@@ -44,12 +44,13 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
setValue(props.value || "");
}, [props.value]);
const { fullWidth, rightText, text, textItems, ...areaProps } = props;
const { fullWidth, rightText, text, textItems, className, ...areaProps } =
props;
return (
<Field
{...props}
className="c__field--textarea"
className={classNames("c__field--textarea", className)}
rightText={rightTextToUse}
>
{/* We disabled linting for this specific line because we consider that the onClick props is only used for */}