Skip to content

Commit 2fec82a

Browse files
Rating input (#147)
This PR aims to introduce the rating input
1 parent 18e9442 commit 2fec82a

4 files changed

Lines changed: 180 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- `RatingInput` component
13+
1014
## [3.10.2] - 2025-09-23
1115

1216
### Fixed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { RatingInput, Form } from "react-hook-form-components";
2+
import { faker } from "@faker-js/faker";
3+
4+
it("rating selection works", () => {
5+
const name = faker.random.alpha(10);
6+
const selectedRating = faker.datatype.number({ min: 1, max: 5 });
7+
8+
cy.mount(
9+
<Form onSubmit={cy.spy().as("onSubmitSpy")}>
10+
<RatingInput name={name} label={name} />
11+
<input type="submit" />
12+
</Form>,
13+
);
14+
15+
// Click on a specific star
16+
cy.get(`input[value="${selectedRating}"]`).click({ force: true });
17+
18+
cy.get("input[type=submit]").click({ force: true });
19+
cy.get("@onSubmitSpy").should("be.calledOnceWith", { [name]: selectedRating });
20+
});
21+
22+
it("default rating value works", () => {
23+
const name = faker.random.alpha(10);
24+
const defaultRating = faker.datatype.number({ min: 1, max: 5 });
25+
26+
cy.mount(
27+
<Form defaultValues={{ [name]: defaultRating }} onSubmit={cy.spy().as("onSubmitSpy")}>
28+
<RatingInput name={name} label={name} />
29+
<input type="submit" />
30+
</Form>,
31+
);
32+
33+
// Verify the default rating is selected
34+
cy.get(`input[value="${defaultRating}"]`).should("be.checked");
35+
36+
cy.get("input[type=submit]").click({ force: true });
37+
cy.get("@onSubmitSpy").should("be.calledOnceWith", { [name]: defaultRating });
38+
});
39+
40+
it("disabled rating works", () => {
41+
const name = faker.random.alpha(10);
42+
43+
cy.mount(
44+
<Form onSubmit={() => {}}>
45+
<RatingInput name={name} label={name} disabled />
46+
</Form>,
47+
);
48+
49+
// All radio buttons should be disabled
50+
cy.get(`input`).each(($input) => {
51+
cy.wrap($input).should("be.disabled");
52+
});
53+
});
54+
55+
it("precision works with half stars", () => {
56+
const name = faker.random.alpha(10);
57+
const halfStarRating = 3.5;
58+
59+
cy.mount(
60+
<Form defaultValues={{ [name]: halfStarRating }} onSubmit={cy.spy().as("onSubmitSpy")}>
61+
<RatingInput name={name} label={name} precision={0.5} />
62+
<input type="submit" />
63+
</Form>,
64+
);
65+
66+
cy.get("input[type=submit]").click({ force: true });
67+
cy.get("@onSubmitSpy").should("be.calledOnceWith", { [name]: halfStarRating });
68+
});
69+
70+
it("onBlur handler gets called", () => {
71+
const name = faker.random.alpha(10);
72+
73+
cy.mount(
74+
<Form onSubmit={() => {}}>
75+
<RatingInput name={name} label={name} onBlur={cy.spy().as("onBlurSpy")} />
76+
</Form>,
77+
);
78+
79+
cy.get(`input[value="3"]`).focus();
80+
cy.get(`input[value="3"]`).blur();
81+
cy.get("@onBlurSpy").should("be.called");
82+
});
83+
84+
it("onChange handler gets called", () => {
85+
const name = faker.random.alpha(10);
86+
const newRating = 4;
87+
88+
cy.mount(
89+
<Form onSubmit={() => {}}>
90+
<RatingInput name={name} label={name} onChange={cy.spy().as("onChangeSpy")} />
91+
</Form>,
92+
);
93+
94+
cy.get(`input[value="${newRating}"]`).click({ force: true });
95+
cy.get("@onChangeSpy").should("be.calledWithMatch", Cypress.sinon.match.any, newRating);
96+
});

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export * from "./lib/types/Typeahead";
77
export * from "./lib/types/LabelValueOption";
88
export * from "./lib/DatePickerInput";
99
export * from "./lib/ColorPickerInput";
10+
export * from "./lib/RatingInput";
1011
export * from "./lib/helpers/dateUtils";
1112
export * from "./lib/helpers/mui";
1213
export * from "./lib/helpers/typeahead";

src/lib/RatingInput.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import Rating, { RatingProps } from "@mui/material/Rating";
2+
import { Controller, FieldValues } from "react-hook-form";
3+
import { FormGroupLayout, FormGroupLayoutProps } from "./FormGroupLayout";
4+
import { useSafeNameId } from "./hooks/useSafeNameId";
5+
import { useFormContext } from "./context/FormContext";
6+
7+
interface RatingInputProps<T extends FieldValues>
8+
extends Omit<RatingProps, "name">,
9+
Omit<FormGroupLayoutProps<T, never>, "onBlur" | "onChange" | "onKeyDown" | "layout" | "addonLeft" | "addonRight" | "addonProps"> {}
10+
11+
const RatingInput = <T extends FieldValues>(props: RatingInputProps<T>) => {
12+
const {
13+
helpText,
14+
label,
15+
labelToolTip,
16+
inputOnly,
17+
hideValidationMessage,
18+
labelStyle,
19+
formGroupId,
20+
inputGroupStyle,
21+
sx,
22+
disabled,
23+
readOnly,
24+
onBlur,
25+
onChange,
26+
...rest
27+
} = props;
28+
const { name, id } = useSafeNameId(props.name, props.id);
29+
const { disabled: formDisabled, control } = useFormContext<T>();
30+
31+
return (
32+
<Controller
33+
control={control}
34+
name={props.name}
35+
render={({ field }) => (
36+
<FormGroupLayout
37+
name={name}
38+
id={id}
39+
helpText={helpText}
40+
label={label}
41+
labelToolTip={labelToolTip}
42+
inputOnly={inputOnly}
43+
hideValidationMessage={hideValidationMessage}
44+
labelStyle={labelStyle}
45+
formGroupId={formGroupId}
46+
inputGroupStyle={inputGroupStyle}
47+
>
48+
<Rating
49+
{...field}
50+
{...rest}
51+
sx={{
52+
...sx,
53+
label: {
54+
// unset the inline-block set by reboot
55+
display: "unset",
56+
},
57+
}}
58+
disabled={disabled || formDisabled}
59+
readOnly={readOnly || formDisabled}
60+
onBlur={(e) => {
61+
if (onBlur) {
62+
onBlur(e);
63+
}
64+
field.onBlur();
65+
}}
66+
onChange={(e, value) => {
67+
if (onChange) {
68+
onChange(e, value);
69+
}
70+
field.onChange(value);
71+
}}
72+
/>
73+
</FormGroupLayout>
74+
)}
75+
/>
76+
);
77+
};
78+
79+
export { RatingInput, RatingInputProps };

0 commit comments

Comments
 (0)