Skip to content

Commit b94770b

Browse files
Merge branch 'neolution-ch:main' into main
2 parents 349cd68 + a10c1ef commit b94770b

9 files changed

Lines changed: 113 additions & 3 deletions

File tree

CHANGELOG.md

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

88
## [Unreleased]
99

10+
## [3.16.0] - 2026-02-05
11+
12+
### Added
13+
14+
- support to allow menu size to fit the longest option into `StaticTypeahead` and `AsyncTypeahead`, via `fitMenuContent`.
15+
1016
## [3.15.1] - 2026-01-12
1117

1218
### Fix
@@ -691,7 +697,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
691697

692698
- Created package :tada:
693699

694-
[unreleased]: https://github.com/neolution-ch/react-hook-form-components/compare/3.15.1...HEAD
700+
[unreleased]: https://github.com/neolution-ch/react-hook-form-components/compare/3.16.0...HEAD
695701
[0.1.2]: https://github.com/neolution-ch/react-hook-form-components/compare/0.1.1...0.1.2
696702
[0.1.1]: https://github.com/neolution-ch/react-hook-form-components/compare/0.1.0...0.1.1
697703
[0.1.0]: https://github.com/neolution-ch/react-hook-form-components/releases/tag/0.1.0
@@ -716,6 +722,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
716722
[0.4.0]: https://github.com/neolution-ch/react-hook-form-components/compare/0.3.0...0.4.0
717723
[0.3.0]: https://github.com/neolution-ch/react-hook-form-components/compare/0.2.0...0.3.0
718724
[0.2.0]: https://github.com/neolution-ch/react-hook-form-components/releases/tag/0.2.0
725+
[3.16.0]: https://github.com/neolution-ch/react-hook-form-components/compare/3.15.1...3.16.0
719726
[3.15.1]: https://github.com/neolution-ch/react-hook-form-components/compare/3.15.0...3.15.1
720727
[3.15.0]: https://github.com/neolution-ch/react-hook-form-components/compare/3.14.0...3.15.0
721728
[3.14.0]: https://github.com/neolution-ch/react-hook-form-components/compare/3.13.1...3.14.0

cypress/cypress/component/Typeahead/AsyncTypeaheadInput.cy.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,6 @@ it("works with fixed options excluded", () => {
931931
});
932932

933933
it("innerRef works correctly", () => {
934-
const { simpleOptions } = generateOptions();
935934
const name = faker.random.alpha(10);
936935
const options = generateOptions();
937936

@@ -964,3 +963,39 @@ it("innerRef works correctly", () => {
964963
cy.get("button[title=focus]").click();
965964
cy.get(`#${name}`).should("be.focused");
966965
});
966+
967+
it("works with fitContentMenu", () => {
968+
const name = faker.random.alpha(10);
969+
const specificOptions = [
970+
{ label: "A Very Long Movie Title That Exceeds Normal Lengths", value: "1" },
971+
{ label: "The Lord of the Rings: The Return of the King", value: "2" },
972+
];
973+
974+
cy.mount(
975+
<div className="p-4">
976+
<Form
977+
onSubmit={() => {
978+
// Nothing to do
979+
}}
980+
>
981+
<AsyncTypeaheadInput
982+
style={{ width: 300 }}
983+
queryFn={async (query: string) => await fetchMock(specificOptions, query, true)}
984+
name={name}
985+
label={name}
986+
fitMenuContent
987+
/>
988+
</Form>
989+
</div>,
990+
);
991+
992+
cy.get(`#${name}`).click();
993+
cy.focused().type(specificOptions[0].label);
994+
cy.get(".MuiInputBase-root").should("have.css", "width", "300px");
995+
cy.get("div[role='presentation']").should(($div) => {
996+
const popperWidth = $div.width() ?? 0;
997+
const paperWidth = $div.find("div.MuiPaper-root").width() ?? 0;
998+
expect(paperWidth).to.be.equal(popperWidth);
999+
expect(popperWidth).to.be.greaterThan(300);
1000+
});
1001+
});

cypress/cypress/component/Typeahead/StaticTypeaheadInput.cy.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,3 +621,37 @@ it("innerRef works correctly", () => {
621621
cy.get("button[title=focus]").click();
622622
cy.get(`#${name}`).should("be.focused");
623623
});
624+
625+
it("works with fitContentMenu", () => {
626+
const { simpleOptions } = generateOptions();
627+
const name = faker.random.alpha(10);
628+
const specificOptions = ["A Very Long Movie Title That Exceeds Normal Lengths", "The Lord of the Rings: The Return of the King"];
629+
630+
cy.mount(
631+
<div className="p-4">
632+
<Form
633+
onSubmit={() => {
634+
// Nothing to do
635+
}}
636+
>
637+
<StaticTypeaheadInput
638+
style={{ width: 300 }}
639+
multiple
640+
name={name}
641+
label={name}
642+
options={[...specificOptions, ...simpleOptions]}
643+
fitMenuContent
644+
/>
645+
</Form>
646+
</div>,
647+
);
648+
649+
cy.get(`#${name}`).click();
650+
cy.get(".MuiInputBase-root").should("have.css", "width", "300px");
651+
cy.get("div[role='presentation']").should(($div) => {
652+
const popperWidth = $div.width() ?? 0;
653+
const paperWidth = $div.find("div.MuiPaper-root").width() ?? 0;
654+
expect(paperWidth).to.be.equal(popperWidth);
655+
expect(popperWidth).to.be.greaterThan(300);
656+
});
657+
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@neolution-ch/react-hook-form-components",
3-
"version": "3.15.1",
3+
"version": "3.16.0",
44
"description": "a collection of react hook form components to reduce boilerplate and figuring out how to integrate react hook form with other libraries",
55
"homepage": "https://neolution-ch.github.io/react-hook-form-components",
66
"repository": {

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export * from "./lib/types/Form";
1010
export * from "./lib/DatePickerInput";
1111
export * from "./lib/ColorPickerInput";
1212
export * from "./lib/RatingInput";
13+
export * from "./lib/components/Typeahead/TypeaheadFitMenuPopper";
1314
export * from "./lib/helpers/dateUtils";
1415
export * from "./lib/helpers/form";
1516
export * from "./lib/helpers/mui";

src/lib/AsyncTypeaheadInput.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { useFormContext } from "./context/FormContext";
1919
import { TypeaheadTextField } from "./components/Typeahead/TypeaheadTextField";
2020
import { FormGroupLayout } from "./FormGroupLayout";
2121
import { LabelValueOption } from "./types/LabelValueOption";
22+
import { TypeaheadFitMenuPopper } from "./components/Typeahead/TypeaheadFitMenuPopper";
2223

2324
interface AsyncTypeaheadInputRef {
2425
resetValues: () => void;
@@ -75,6 +76,7 @@ const AsyncTypeaheadInput = <T extends FieldValues>(props: AsyncTypeaheadInputPr
7576
autocompleteProps,
7677
fixedOptions,
7778
withFixedOptionsInValue = true,
79+
fitMenuContent,
7880
} = props;
7981

8082
const [options, setOptions] = useState<TypeaheadOptions>(defaultOptions);
@@ -145,6 +147,10 @@ const AsyncTypeaheadInput = <T extends FieldValues>(props: AsyncTypeaheadInputPr
145147
<Autocomplete<TypeaheadOption, boolean, boolean, boolean>
146148
{...autocompleteProps}
147149
{...field}
150+
slots={{
151+
popper: fitMenuContent ? TypeaheadFitMenuPopper : undefined,
152+
...autocompleteProps?.slots,
153+
}}
148154
id={id}
149155
multiple={multiple}
150156
loading={isLoading}

src/lib/StaticTypeaheadInput.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { TypeaheadTextField } from "./components/Typeahead/TypeaheadTextField";
2323
import { FormGroupLayout } from "./FormGroupLayout";
2424
import { LabelValueOption } from "./types/LabelValueOption";
25+
import { TypeaheadFitMenuPopper } from "./components/Typeahead/TypeaheadFitMenuPopper";
2526

2627
interface StaticTypeaheadInputProps<T extends FieldValues> extends CommonTypeaheadProps<T> {
2728
options: TypeaheadOptions;
@@ -66,6 +67,7 @@ const StaticTypeaheadInput = <T extends FieldValues>(props: StaticTypeaheadInput
6667
fixedOptions,
6768
withFixedOptionsInValue = true,
6869
innerRef,
70+
fitMenuContent,
6971
} = props;
7072

7173
const [page, setPage] = useState(1);
@@ -118,6 +120,10 @@ const StaticTypeaheadInput = <T extends FieldValues>(props: StaticTypeaheadInput
118120
<Autocomplete<TypeaheadOption, boolean, boolean, boolean>
119121
{...autocompleteProps}
120122
{...field}
123+
slots={{
124+
popper: fitMenuContent ? TypeaheadFitMenuPopper : undefined,
125+
...autocompleteProps?.slots,
126+
}}
121127
id={id}
122128
multiple={multiple}
123129
groupBy={useGroupBy ? groupOptions : undefined}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Popper, { PopperProps } from "@mui/material/Popper";
2+
3+
const TypeaheadFitMenuPopper = (props: PopperProps) => {
4+
const { anchorEl } = props;
5+
6+
return (
7+
<Popper
8+
{...props}
9+
style={{
10+
// ensure popper is at least as wide as the input field
11+
minWidth: (anchorEl as HTMLElement)?.clientWidth,
12+
// ensure popper fits the longest option width
13+
width: "fit-content",
14+
}}
15+
placement="bottom-start"
16+
/>
17+
);
18+
};
19+
20+
export { TypeaheadFitMenuPopper };

src/lib/types/Typeahead.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface CommonTypeaheadProps<T extends FieldValues>
2525
fixedOptions?: TypeaheadOptions;
2626
withFixedOptionsInValue?: boolean;
2727
innerRef?: MutableRefObject<HTMLInputElement | null>;
28+
fitMenuContent?: boolean;
2829
getOptionDisabled?: (option: TypeaheadOption) => boolean;
2930
onChange?: (selected: string | string[]) => void;
3031
onInputChange?: (text: string, reason: AutocompleteInputChangeReason) => void;

0 commit comments

Comments
 (0)