@simplybusiness/mobius 5.5.0 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/components/Combobox/Combobox.js +56 -33
- package/dist/cjs/components/Combobox/Combobox.js.map +1 -1
- package/dist/cjs/components/Combobox/Listbox.js +58 -0
- package/dist/cjs/components/Combobox/Listbox.js.map +1 -0
- package/dist/cjs/components/Combobox/Option.js +44 -0
- package/dist/cjs/components/Combobox/Option.js.map +1 -0
- package/dist/cjs/components/Combobox/fixtures.js +115 -0
- package/dist/cjs/components/Combobox/fixtures.js.map +1 -1
- package/dist/cjs/components/Combobox/useComboboxHighlight.js +86 -0
- package/dist/cjs/components/Combobox/useComboboxHighlight.js.map +1 -0
- package/dist/cjs/components/Combobox/utils.js +46 -0
- package/dist/cjs/components/Combobox/utils.js.map +1 -0
- package/dist/cjs/components/TextArea/TextArea.js +4 -4
- package/dist/cjs/components/TextArea/TextArea.js.map +1 -1
- package/dist/cjs/components/TextAreaInput/TextAreaInput.js +6 -3
- package/dist/cjs/components/TextAreaInput/TextAreaInput.js.map +1 -1
- package/dist/cjs/components/TextField/TextField.js +4 -3
- package/dist/cjs/components/TextField/TextField.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/components/Combobox/Combobox.js +55 -32
- package/dist/esm/components/Combobox/Combobox.js.map +1 -1
- package/dist/esm/components/Combobox/Listbox.js +43 -0
- package/dist/esm/components/Combobox/Listbox.js.map +1 -0
- package/dist/esm/components/Combobox/Option.js +29 -0
- package/dist/esm/components/Combobox/Option.js.map +1 -0
- package/dist/esm/components/Combobox/fixtures.js +109 -0
- package/dist/esm/components/Combobox/fixtures.js.map +1 -1
- package/dist/esm/components/Combobox/types.js.map +1 -1
- package/dist/esm/components/Combobox/useComboboxHighlight.js +76 -0
- package/dist/esm/components/Combobox/useComboboxHighlight.js.map +1 -0
- package/dist/esm/components/Combobox/utils.js +20 -0
- package/dist/esm/components/Combobox/utils.js.map +1 -0
- package/dist/esm/components/TextArea/TextArea.js +4 -4
- package/dist/esm/components/TextArea/TextArea.js.map +1 -1
- package/dist/esm/components/TextAreaInput/TextAreaInput.js +6 -3
- package/dist/esm/components/TextAreaInput/TextAreaInput.js.map +1 -1
- package/dist/esm/components/TextField/TextField.js +4 -3
- package/dist/esm/components/TextField/TextField.js.map +1 -1
- package/dist/types/components/Combobox/Combobox.d.ts +1 -1
- package/dist/types/components/Combobox/Combobox.stories.d.ts +4 -1
- package/dist/types/components/Combobox/Listbox.d.ts +10 -0
- package/dist/types/components/Combobox/Option.d.ts +2 -0
- package/dist/types/components/Combobox/fixtures.d.ts +5 -0
- package/dist/types/components/Combobox/types.d.ts +17 -2
- package/dist/types/components/Combobox/useComboboxHighlight.d.ts +10 -0
- package/dist/types/components/Combobox/useComboboxHighlight.test.d.ts +1 -0
- package/dist/types/components/Combobox/utils.d.ts +6 -0
- package/dist/types/components/Combobox/utils.test.d.ts +1 -0
- package/dist/types/components/TextArea/TextArea.d.ts +2 -2
- package/dist/types/components/TextAreaInput/TextAreaInput.d.ts +3 -1
- package/package.json +1 -1
- package/src/components/Combobox/Combobox.css +51 -4
- package/src/components/Combobox/Combobox.mdx +147 -0
- package/src/components/Combobox/Combobox.stories.tsx +47 -2
- package/src/components/Combobox/Combobox.test.tsx +535 -316
- package/src/components/Combobox/Combobox.tsx +78 -58
- package/src/components/Combobox/Listbox.tsx +74 -0
- package/src/components/Combobox/Option.tsx +41 -0
- package/src/components/Combobox/fixtures.tsx +111 -0
- package/src/components/Combobox/types.tsx +22 -4
- package/src/components/Combobox/useComboboxHighlight.test.tsx +242 -0
- package/src/components/Combobox/useComboboxHighlight.tsx +88 -0
- package/src/components/Combobox/utils.test.tsx +120 -0
- package/src/components/Combobox/utils.tsx +50 -0
- package/src/components/TextArea/TextArea.tsx +6 -6
- package/src/components/TextAreaInput/TextAreaInput.tsx +16 -4
- package/src/components/TextField/TextField.test.tsx +8 -0
- package/src/components/TextField/TextField.tsx +3 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/TextArea/TextArea.tsx"],"sourcesContent":["\"use client\";\n\nimport { Ref,
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/TextArea/TextArea.tsx"],"sourcesContent":["\"use client\";\n\nimport classNames from \"classnames/dedupe\";\nimport { Ref, RefAttributes, forwardRef } from \"react\";\nimport {\n UseTextFieldProps,\n useTextField,\n useValidationClasses,\n} from \"../../hooks\";\nimport { ForwardedRefComponent } from \"../../types/components\";\nimport { DOMProps } from \"../../types/dom\";\nimport { ErrorMessage } from \"../ErrorMessage\";\nimport { Label } from \"../Label\";\nimport { Stack } from \"../Stack\";\nimport { TextAreaInput } from \"../TextAreaInput\";\n\nexport type TextAreaElementType = HTMLTextAreaElement;\n\nexport interface TextAreaProps\n extends UseTextFieldProps,\n DOMProps,\n RefAttributes<TextAreaElementType> {\n className?: string;\n errorMessage?: string;\n}\n\nexport type TextAreaRef = Ref<TextAreaElementType>;\n\nconst TextArea: ForwardedRefComponent<TextAreaProps, TextAreaElementType> =\n forwardRef((props: TextAreaProps, ref: TextAreaRef) => {\n const {\n isDisabled,\n className,\n errorMessage,\n label,\n validationState,\n isInvalid,\n ...otherProps\n } = props;\n\n const { inputProps, labelProps, errorMessageProps } = useTextField(props);\n\n // Set class name for atom, including interaction state\n const classes = classNames(\"mobius\", \"mobius-text-area\", className);\n const validationClasses = useValidationClasses({\n validationState,\n isInvalid,\n });\n const inputClasses = classNames(\n \"mobius-text-area__input\",\n validationClasses,\n );\n const labelClasses = classNames(\n {\n \"--is-disabled\": isDisabled,\n },\n validationClasses,\n );\n\n return (\n <Stack className={classes} gap=\"xs\">\n {label && (\n <Label {...labelProps} className={labelClasses}>\n {props.label}\n </Label>\n )}\n <TextAreaInput\n {...otherProps}\n {...inputProps}\n ref={ref}\n className={inputClasses}\n isDisabled={isDisabled}\n aria-invalid={errorMessage != null}\n />\n <ErrorMessage {...errorMessageProps} errorMessage={errorMessage} />\n </Stack>\n );\n });\n\nTextArea.displayName = \"TextArea\";\nexport { TextArea };\n"],"names":["classNames","forwardRef","useTextField","useValidationClasses","ErrorMessage","Label","Stack","TextAreaInput","TextArea","props","ref","isDisabled","className","errorMessage","label","validationState","isInvalid","otherProps","inputProps","labelProps","errorMessageProps","classes","validationClasses","inputClasses","labelClasses","gap","aria-invalid","displayName"],"mappings":"AAAA;;AAEA,OAAOA,gBAAgB,oBAAoB;AAC3C,SAA6BC,UAAU,QAAQ,QAAQ;AACvD,SAEEC,YAAY,EACZC,oBAAoB,QACf,cAAc;AAGrB,SAASC,YAAY,QAAQ,kBAAkB;AAC/C,SAASC,KAAK,QAAQ,WAAW;AACjC,SAASC,KAAK,QAAQ,WAAW;AACjC,SAASC,aAAa,QAAQ,mBAAmB;AAcjD,MAAMC,yBACJP,WAAW,CAACQ,OAAsBC;IAChC,MAAM,EACJC,UAAU,EACVC,SAAS,EACTC,YAAY,EACZC,KAAK,EACLC,eAAe,EACfC,SAAS,EACT,GAAGC,YACJ,GAAGR;IAEJ,MAAM,EAAES,UAAU,EAAEC,UAAU,EAAEC,iBAAiB,EAAE,GAAGlB,aAAaO;IAEnE,uDAAuD;IACvD,MAAMY,UAAUrB,WAAW,UAAU,oBAAoBY;IACzD,MAAMU,oBAAoBnB,qBAAqB;QAC7CY;QACAC;IACF;IACA,MAAMO,eAAevB,WACnB,2BACAsB;IAEF,MAAME,eAAexB,WACnB;QACE,iBAAiBW;IACnB,GACAW;IAGF,qBACE,MAAChB;QAAMM,WAAWS;QAASI,KAAI;;YAC5BX,uBACC,KAACT;gBAAO,GAAGc,UAAU;gBAAEP,WAAWY;0BAC/Bf,MAAMK,KAAK;;0BAGhB,KAACP;gBACE,GAAGU,UAAU;gBACb,GAAGC,UAAU;gBACdR,KAAKA;gBACLE,WAAWW;gBACXZ,YAAYA;gBACZe,gBAAcb,gBAAgB;;0BAEhC,KAACT;gBAAc,GAAGgB,iBAAiB;gBAAEP,cAAcA;;;;AAGzD;AAEFL,SAASmB,WAAW,GAAG;AACvB,SAASnB,QAAQ,GAAG"}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef } from "react";
|
|
3
2
|
import classNames from "classnames/dedupe";
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
4
|
const TextAreaInput = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
5
5
|
// Extract interaction props:
|
|
6
|
-
const { isSelected, isDisabled, ...otherProps } = props;
|
|
6
|
+
const { isSelected, isDisabled, isReadOnly, isRequired, ...otherProps } = props;
|
|
7
7
|
const classes = classNames("mobius", "mobius-text-area__input", {
|
|
8
8
|
"--is-disabled": isDisabled,
|
|
9
|
-
"--is-selected": isSelected
|
|
9
|
+
"--is-selected": isSelected,
|
|
10
|
+
"--is-required": isRequired
|
|
10
11
|
}, otherProps.className);
|
|
11
12
|
return /*#__PURE__*/ _jsx("textarea", {
|
|
12
13
|
ref: ref,
|
|
13
14
|
...otherProps,
|
|
15
|
+
required: isRequired,
|
|
16
|
+
readOnly: isReadOnly,
|
|
14
17
|
className: classes
|
|
15
18
|
});
|
|
16
19
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/TextAreaInput/TextAreaInput.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/TextAreaInput/TextAreaInput.tsx"],"sourcesContent":["import classNames from \"classnames/dedupe\";\nimport { forwardRef, Ref, RefAttributes } from \"react\";\nimport { ForwardedRefComponent } from \"../../types/components\";\nimport { DOMProps } from \"../../types/dom\";\n\nexport type TextAreaInputElementType = HTMLTextAreaElement;\n\nexport interface InteractionStateProps {\n isSelected?: boolean;\n isDisabled?: boolean;\n}\n\nexport interface TextAreaInputProps\n extends DOMProps,\n InteractionStateProps,\n RefAttributes<TextAreaInputElementType> {\n className?: string;\n isReadOnly?: boolean;\n isRequired?: boolean;\n}\n\nexport type TextAreaInputRef = Ref<TextAreaInputElementType>;\n\nconst TextAreaInput: ForwardedRefComponent<\n TextAreaInputProps,\n TextAreaInputElementType\n> = forwardRef((props: TextAreaInputProps, ref: TextAreaInputRef) => {\n // Extract interaction props:\n const { isSelected, isDisabled, isReadOnly, isRequired, ...otherProps } =\n props;\n\n const classes = classNames(\n \"mobius\",\n \"mobius-text-area__input\",\n {\n \"--is-disabled\": isDisabled,\n \"--is-selected\": isSelected,\n \"--is-required\": isRequired,\n },\n otherProps.className,\n );\n\n return (\n <textarea\n ref={ref}\n {...otherProps}\n required={isRequired}\n readOnly={isReadOnly}\n className={classes}\n />\n );\n});\n\nTextAreaInput.displayName = \"TextAreaInput\";\nexport { TextAreaInput };\n"],"names":["classNames","forwardRef","TextAreaInput","props","ref","isSelected","isDisabled","isReadOnly","isRequired","otherProps","classes","className","textarea","required","readOnly","displayName"],"mappings":";AAAA,OAAOA,gBAAgB,oBAAoB;AAC3C,SAASC,UAAU,QAA4B,QAAQ;AAsBvD,MAAMC,8BAGFD,WAAW,CAACE,OAA2BC;IACzC,6BAA6B;IAC7B,MAAM,EAAEC,UAAU,EAAEC,UAAU,EAAEC,UAAU,EAAEC,UAAU,EAAE,GAAGC,YAAY,GACrEN;IAEF,MAAMO,UAAUV,WACd,UACA,2BACA;QACE,iBAAiBM;QACjB,iBAAiBD;QACjB,iBAAiBG;IACnB,GACAC,WAAWE,SAAS;IAGtB,qBACE,KAACC;QACCR,KAAKA;QACJ,GAAGK,UAAU;QACdI,UAAUL;QACVM,UAAUP;QACVI,WAAWD;;AAGjB;AAEAR,cAAca,WAAW,GAAG;AAC5B,SAASb,aAAa,GAAG"}
|
|
@@ -5,10 +5,10 @@ import { forwardRef } from "react";
|
|
|
5
5
|
import { useTextField, useValidationClasses } from "../../hooks";
|
|
6
6
|
import { ErrorMessage } from "../ErrorMessage";
|
|
7
7
|
import { Label } from "../Label";
|
|
8
|
-
import { adornmentWithClassName } from "./adornmentWithClassName";
|
|
9
8
|
import { Stack } from "../Stack";
|
|
9
|
+
import { adornmentWithClassName } from "./adornmentWithClassName";
|
|
10
10
|
const TextField = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
11
|
-
const { isDisabled, type = "text", validationState, isInvalid, className, label, errorMessage, children, isRequired, prefixInside, prefixOutside, suffixInside, suffixOutside, ...otherProps } = props;
|
|
11
|
+
const { isDisabled, isReadOnly, type = "text", validationState, isInvalid, className, label, errorMessage, children, isRequired, prefixInside, prefixOutside, suffixInside, suffixOutside, ...otherProps } = props;
|
|
12
12
|
const { inputProps, labelProps, errorMessageProps } = useTextField({
|
|
13
13
|
...props,
|
|
14
14
|
"aria-errormessage": errorMessage
|
|
@@ -54,7 +54,8 @@ const TextField = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
|
54
54
|
...inputProps,
|
|
55
55
|
ref: ref,
|
|
56
56
|
type: type,
|
|
57
|
-
className: inputClasses
|
|
57
|
+
className: inputClasses,
|
|
58
|
+
readOnly: isReadOnly
|
|
58
59
|
}),
|
|
59
60
|
adornmentWithClassName(suffixInside, "mobius-text-field__suffix-inside")
|
|
60
61
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/TextField/TextField.tsx"],"sourcesContent":["\"use client\";\n\nimport classNames from \"classnames/dedupe\";\nimport {\n HTMLInputTypeAttribute,\n ReactElement,\n ReactNode,\n Ref,\n RefAttributes,\n forwardRef,\n} from \"react\";\nimport {\n UseTextFieldProps,\n useTextField,\n useValidationClasses,\n} from \"../../hooks\";\nimport { DOMProps, FocusEvents } from \"../../types\";\nimport { ForwardedRefComponent } from \"../../types/components\";\nimport { ErrorMessage } from \"../ErrorMessage\";\nimport { Label } from \"../Label\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/TextField/TextField.tsx"],"sourcesContent":["\"use client\";\n\nimport classNames from \"classnames/dedupe\";\nimport {\n HTMLInputTypeAttribute,\n ReactElement,\n ReactNode,\n Ref,\n RefAttributes,\n forwardRef,\n} from \"react\";\nimport {\n UseTextFieldProps,\n useTextField,\n useValidationClasses,\n} from \"../../hooks\";\nimport { DOMProps, FocusEvents } from \"../../types\";\nimport { ForwardedRefComponent } from \"../../types/components\";\nimport { ErrorMessage } from \"../ErrorMessage\";\nimport { Label } from \"../Label\";\nimport { Stack } from \"../Stack\";\nimport { adornmentWithClassName } from \"./adornmentWithClassName\";\n\nexport type TextFieldElementType = HTMLInputElement;\nexport interface TextFieldProps\n extends DOMProps,\n FocusEvents,\n UseTextFieldProps,\n RefAttributes<TextFieldElementType> {\n className?: string;\n errorMessage?: string;\n children?: ReactNode;\n label?: string;\n type?: Exclude<\n HTMLInputTypeAttribute,\n | \"button\"\n | \"checkbox\"\n | \"color\"\n | \"date\"\n | \"datetime-local\"\n | \"file\"\n | \"image\"\n | \"month\"\n | \"radio\"\n | \"range\"\n | \"reset\"\n | \"submit\"\n | \"week\"\n >;\n prefixInside?: ReactElement;\n prefixOutside?: ReactElement;\n suffixInside?: ReactElement;\n suffixOutside?: ReactElement;\n}\n\nexport type TextFieldRef = Ref<TextFieldElementType>;\n\nconst TextField: ForwardedRefComponent<TextFieldProps, TextFieldElementType> =\n forwardRef((props: TextFieldProps, ref: TextFieldRef) => {\n const {\n isDisabled,\n isReadOnly,\n type = \"text\",\n validationState,\n isInvalid,\n className,\n label,\n errorMessage,\n children,\n isRequired,\n prefixInside,\n prefixOutside,\n suffixInside,\n suffixOutside,\n ...otherProps\n } = props;\n\n const { inputProps, labelProps, errorMessageProps } = useTextField({\n ...props,\n \"aria-errormessage\": errorMessage,\n });\n\n const hidden = type === \"hidden\";\n\n const validationClasses = useValidationClasses({\n validationState,\n isInvalid,\n });\n\n const textfieldClasses = {\n \"--is-disabled\": isDisabled,\n \"--is-required\": typeof isRequired === \"boolean\" && isRequired,\n \"--is-optional\": typeof isRequired === \"boolean\" && !isRequired,\n \"--is-hidden\": hidden,\n [className || \"\"]: true,\n };\n\n const sharedClasses = classNames(validationClasses, textfieldClasses);\n\n const labelClasses = classNames(\n {\n \"--is-disabled\": isDisabled,\n },\n validationClasses,\n );\n\n const containerClasses = classNames(\n \"mobius\",\n \"mobius-text-field\",\n sharedClasses,\n );\n\n const inputClasses = classNames(\n \"mobius\",\n \"mobius-text-field__input\",\n sharedClasses,\n );\n\n const inputWrapperClasses = classNames(\n \"mobius-text-field__input-wrapper\",\n sharedClasses,\n );\n\n return (\n <Stack gap=\"xs\" className={containerClasses}>\n {label && !hidden && (\n <Label {...labelProps} className={labelClasses}>\n {label}\n </Label>\n )}\n <div className=\"mobius-text-field__inner-container\">\n {adornmentWithClassName(\n prefixOutside,\n \"mobius-text-field__prefix-outside\",\n )}\n <div className={inputWrapperClasses}>\n {adornmentWithClassName(\n prefixInside,\n \"mobius-text-field__prefix-inside\",\n )}\n <input\n {...otherProps}\n {...inputProps}\n ref={ref}\n type={type}\n className={inputClasses}\n readOnly={isReadOnly}\n />\n {adornmentWithClassName(\n suffixInside,\n \"mobius-text-field__suffix-inside\",\n )}\n </div>\n {adornmentWithClassName(\n suffixOutside,\n \"mobius-text-field__suffix-outside\",\n )}\n </div>\n {children && (\n <div className=\"mobius-text-field__children\">{children}</div>\n )}\n\n <ErrorMessage {...errorMessageProps} errorMessage={errorMessage} />\n </Stack>\n );\n });\n\nTextField.displayName = \"TextField\";\nexport { TextField };\n"],"names":["classNames","forwardRef","useTextField","useValidationClasses","ErrorMessage","Label","Stack","adornmentWithClassName","TextField","props","ref","isDisabled","isReadOnly","type","validationState","isInvalid","className","label","errorMessage","children","isRequired","prefixInside","prefixOutside","suffixInside","suffixOutside","otherProps","inputProps","labelProps","errorMessageProps","hidden","validationClasses","textfieldClasses","sharedClasses","labelClasses","containerClasses","inputClasses","inputWrapperClasses","gap","div","input","readOnly","displayName"],"mappings":"AAAA;;AAEA,OAAOA,gBAAgB,oBAAoB;AAC3C,SAMEC,UAAU,QACL,QAAQ;AACf,SAEEC,YAAY,EACZC,oBAAoB,QACf,cAAc;AAGrB,SAASC,YAAY,QAAQ,kBAAkB;AAC/C,SAASC,KAAK,QAAQ,WAAW;AACjC,SAASC,KAAK,QAAQ,WAAW;AACjC,SAASC,sBAAsB,QAAQ,2BAA2B;AAoClE,MAAMC,0BACJP,WAAW,CAACQ,OAAuBC;IACjC,MAAM,EACJC,UAAU,EACVC,UAAU,EACVC,OAAO,MAAM,EACbC,eAAe,EACfC,SAAS,EACTC,SAAS,EACTC,KAAK,EACLC,YAAY,EACZC,QAAQ,EACRC,UAAU,EACVC,YAAY,EACZC,aAAa,EACbC,YAAY,EACZC,aAAa,EACb,GAAGC,YACJ,GAAGhB;IAEJ,MAAM,EAAEiB,UAAU,EAAEC,UAAU,EAAEC,iBAAiB,EAAE,GAAG1B,aAAa;QACjE,GAAGO,KAAK;QACR,qBAAqBS;IACvB;IAEA,MAAMW,SAAShB,SAAS;IAExB,MAAMiB,oBAAoB3B,qBAAqB;QAC7CW;QACAC;IACF;IAEA,MAAMgB,mBAAmB;QACvB,iBAAiBpB;QACjB,iBAAiB,OAAOS,eAAe,aAAaA;QACpD,iBAAiB,OAAOA,eAAe,aAAa,CAACA;QACrD,eAAeS;QACf,CAACb,aAAa,GAAG,EAAE;IACrB;IAEA,MAAMgB,gBAAgBhC,WAAW8B,mBAAmBC;IAEpD,MAAME,eAAejC,WACnB;QACE,iBAAiBW;IACnB,GACAmB;IAGF,MAAMI,mBAAmBlC,WACvB,UACA,qBACAgC;IAGF,MAAMG,eAAenC,WACnB,UACA,4BACAgC;IAGF,MAAMI,sBAAsBpC,WAC1B,oCACAgC;IAGF,qBACE,MAAC1B;QAAM+B,KAAI;QAAKrB,WAAWkB;;YACxBjB,SAAS,CAACY,wBACT,KAACxB;gBAAO,GAAGsB,UAAU;gBAAEX,WAAWiB;0BAC/BhB;;0BAGL,MAACqB;gBAAItB,WAAU;;oBACZT,uBACCe,eACA;kCAEF,MAACgB;wBAAItB,WAAWoB;;4BACb7B,uBACCc,cACA;0CAEF,KAACkB;gCACE,GAAGd,UAAU;gCACb,GAAGC,UAAU;gCACdhB,KAAKA;gCACLG,MAAMA;gCACNG,WAAWmB;gCACXK,UAAU5B;;4BAEXL,uBACCgB,cACA;;;oBAGHhB,uBACCiB,eACA;;;YAGHL,0BACC,KAACmB;gBAAItB,WAAU;0BAA+BG;;0BAGhD,KAACf;gBAAc,GAAGwB,iBAAiB;gBAAEV,cAAcA;;;;AAGzD;AAEFV,UAAUiC,WAAW,GAAG;AACxB,SAASjC,SAAS,GAAG"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { ComboboxProps, ComboboxElementType } from "./types";
|
|
2
1
|
import type { ForwardedRefComponent } from "../../types";
|
|
2
|
+
import type { ComboboxElementType, ComboboxProps } from "./types";
|
|
3
3
|
export declare const Combobox: ForwardedRefComponent<ComboboxProps, ComboboxElementType>;
|
|
@@ -3,5 +3,8 @@ import { Combobox } from ".";
|
|
|
3
3
|
type StoryType = StoryObj<typeof Combobox>;
|
|
4
4
|
declare const meta: Meta<typeof Combobox>;
|
|
5
5
|
export declare const Default: StoryType;
|
|
6
|
-
export declare const
|
|
6
|
+
export declare const OptionsObject: StoryType;
|
|
7
|
+
export declare const GroupedOptions: StoryType;
|
|
8
|
+
export declare const WithIcon: StoryType;
|
|
9
|
+
export declare const StateSelector: StoryType;
|
|
7
10
|
export default meta;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComboboxOption, ComboboxOptions } from "./types";
|
|
2
|
+
export type ListboxProps = {
|
|
3
|
+
id: string;
|
|
4
|
+
options: ComboboxOptions;
|
|
5
|
+
highlightedIndex: number;
|
|
6
|
+
highlightedGroupIndex: number;
|
|
7
|
+
onOptionSelect: (option: ComboboxOption) => void;
|
|
8
|
+
expanded?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare const Listbox: ({ id, options, highlightedIndex, highlightedGroupIndex, onOptionSelect, expanded, }: ListboxProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { Ref } from "react";
|
|
1
|
+
import type { ReactElement, Ref } from "react";
|
|
2
2
|
import type { TextFieldElementType, TextFieldProps } from "../TextField";
|
|
3
3
|
export type ComboboxProps = TextFieldProps & {
|
|
4
4
|
/** The list of options to display in the dropdown */
|
|
5
|
-
options:
|
|
5
|
+
options: ComboboxOptions | ((filter: string) => ComboboxOptions) | ((filter: string) => Promise<ComboboxOptions>);
|
|
6
|
+
/** An icon to display in TextField (left side) */
|
|
7
|
+
icon?: ReactElement;
|
|
6
8
|
/** The default value of the selected option */
|
|
7
9
|
defaultValue?: string | undefined;
|
|
8
10
|
/** Callback when the selected option changes */
|
|
@@ -12,5 +14,18 @@ export type ComboboxOption = string | {
|
|
|
12
14
|
label: string;
|
|
13
15
|
value: string;
|
|
14
16
|
};
|
|
17
|
+
export type ComboboxOptionGroup = {
|
|
18
|
+
heading: string;
|
|
19
|
+
options: ComboboxOption[];
|
|
20
|
+
};
|
|
21
|
+
export type ComboboxOptions = ComboboxOption[] | ComboboxOptionGroup[];
|
|
15
22
|
export type ComboboxElementType = TextFieldElementType;
|
|
16
23
|
export type ComboboxRef = Ref<ComboboxElementType>;
|
|
24
|
+
export type ComboboxOptionProps = {
|
|
25
|
+
option: ComboboxOption;
|
|
26
|
+
index: number;
|
|
27
|
+
groupIndex?: number;
|
|
28
|
+
isHighlighted: boolean;
|
|
29
|
+
onOptionSelect: (option: ComboboxOption) => void;
|
|
30
|
+
id: string;
|
|
31
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComboboxOptions } from "./types";
|
|
2
|
+
export declare function useComboboxHighlight(options: ComboboxOptions): {
|
|
3
|
+
highlightedIndex: number;
|
|
4
|
+
highlightedGroupIndex: number;
|
|
5
|
+
highlightPreviousOption: () => void;
|
|
6
|
+
highlightNextOption: () => void;
|
|
7
|
+
highlightFirstOption: () => void;
|
|
8
|
+
highlightLastOption: () => void;
|
|
9
|
+
clearHighlight: () => void;
|
|
10
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ComboboxOption, ComboboxOptionGroup, ComboboxOptions } from "./types";
|
|
2
|
+
export declare function isOptionGroup(options: ComboboxOptions): options is ComboboxOptionGroup[];
|
|
3
|
+
export declare const getOptionValue: (option: ComboboxOption | undefined) => string | undefined;
|
|
4
|
+
export declare const getOptionLabel: (option: ComboboxOption | undefined) => string | undefined;
|
|
5
|
+
export declare function filterOptions(options: ComboboxOptions, inputValue: string): ComboboxOptions;
|
|
6
|
+
export declare function clamp(value: number, min: number, max: number): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Ref, RefAttributes } from "react";
|
|
2
|
-
import { DOMProps } from "../../types/dom";
|
|
3
|
-
import { ForwardedRefComponent } from "../../types/components";
|
|
4
2
|
import { UseTextFieldProps } from "../../hooks";
|
|
3
|
+
import { ForwardedRefComponent } from "../../types/components";
|
|
4
|
+
import { DOMProps } from "../../types/dom";
|
|
5
5
|
export type TextAreaElementType = HTMLTextAreaElement;
|
|
6
6
|
export interface TextAreaProps extends UseTextFieldProps, DOMProps, RefAttributes<TextAreaElementType> {
|
|
7
7
|
className?: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Ref, RefAttributes } from "react";
|
|
2
|
-
import { DOMProps } from "../../types/dom";
|
|
3
2
|
import { ForwardedRefComponent } from "../../types/components";
|
|
3
|
+
import { DOMProps } from "../../types/dom";
|
|
4
4
|
export type TextAreaInputElementType = HTMLTextAreaElement;
|
|
5
5
|
export interface InteractionStateProps {
|
|
6
6
|
isSelected?: boolean;
|
|
@@ -8,6 +8,8 @@ export interface InteractionStateProps {
|
|
|
8
8
|
}
|
|
9
9
|
export interface TextAreaInputProps extends DOMProps, InteractionStateProps, RefAttributes<TextAreaInputElementType> {
|
|
10
10
|
className?: string;
|
|
11
|
+
isReadOnly?: boolean;
|
|
12
|
+
isRequired?: boolean;
|
|
11
13
|
}
|
|
12
14
|
export type TextAreaInputRef = Ref<TextAreaInputElementType>;
|
|
13
15
|
declare const TextAreaInput: ForwardedRefComponent<TextAreaInputProps, TextAreaInputElementType>;
|
package/package.json
CHANGED
|
@@ -1,25 +1,51 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* --combobox-max-width: 562px; */
|
|
3
|
+
interpolate-size: allow-keywords;
|
|
4
|
+
--combobox-border-color: #ccc;
|
|
5
|
+
--combobox-group-color: var(--color-text);
|
|
6
|
+
--combobox-group-background-color: #dad6f7;
|
|
7
|
+
--combobox-selected-background-color: var(--color-primary);
|
|
8
|
+
--listbox-height: 200px;
|
|
9
|
+
--listbox-gap: 4px;
|
|
10
|
+
--option-padding: var(--size-xs) var(--size-lg);
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
.mobius-combobox {
|
|
2
14
|
position: relative;
|
|
15
|
+
width: 100%;
|
|
3
16
|
}
|
|
4
17
|
|
|
5
18
|
.mobius-combobox__list {
|
|
6
19
|
position: absolute;
|
|
7
20
|
margin: 0;
|
|
21
|
+
margin-top: var(--listbox-gap);
|
|
8
22
|
padding: 0;
|
|
9
23
|
top: 100%;
|
|
10
24
|
left: 0;
|
|
11
25
|
right: 0;
|
|
12
26
|
z-index: 1000;
|
|
13
27
|
background-color: #fff;
|
|
14
|
-
border: 1px solid
|
|
15
|
-
border-
|
|
28
|
+
border: 1px solid var(--combobox-border-color);
|
|
29
|
+
border-radius: var(--radius-1);
|
|
16
30
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
17
|
-
max-height:
|
|
31
|
+
max-height: var(--listbox-height);
|
|
18
32
|
overflow-y: auto;
|
|
33
|
+
height: 0;
|
|
34
|
+
opacity: 0;
|
|
35
|
+
transition:
|
|
36
|
+
box-shadow 0.3s ease,
|
|
37
|
+
height 0.3s ease,
|
|
38
|
+
opacity 0.3s ease;
|
|
39
|
+
transition-behavior: allow-discrete;
|
|
40
|
+
|
|
41
|
+
&.expanded {
|
|
42
|
+
opacity: 1;
|
|
43
|
+
height: auto;
|
|
44
|
+
}
|
|
19
45
|
}
|
|
20
46
|
|
|
21
47
|
.mobius-combobox__option {
|
|
22
|
-
padding:
|
|
48
|
+
padding: var(--option-padding);
|
|
23
49
|
cursor: pointer;
|
|
24
50
|
}
|
|
25
51
|
|
|
@@ -28,3 +54,24 @@
|
|
|
28
54
|
background-color: var(--color-primary);
|
|
29
55
|
color: var(--color-neutral-100);
|
|
30
56
|
}
|
|
57
|
+
|
|
58
|
+
/* Group styles */
|
|
59
|
+
[role="group"] {
|
|
60
|
+
margin: 0;
|
|
61
|
+
padding: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
[role="group"] > [role="presentation"] {
|
|
65
|
+
display: block;
|
|
66
|
+
margin: 0;
|
|
67
|
+
padding: var(--option-padding);
|
|
68
|
+
font-weight: bold;
|
|
69
|
+
color: var(--combobox-group-color);
|
|
70
|
+
background-color: var(--combobox-group-background-color);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
[role="option"] {
|
|
74
|
+
position: relative;
|
|
75
|
+
display: block;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { ArgTypes, Meta, Story } from "@storybook/blocks";
|
|
2
|
+
import { Combobox } from "./Combobox";
|
|
3
|
+
import * as ComboboxStories from "./Combobox.stories";
|
|
4
|
+
|
|
5
|
+
<Meta of={ComboboxStories} />
|
|
6
|
+
|
|
7
|
+
# Combobox
|
|
8
|
+
|
|
9
|
+
A `Combobox` is an enhanced `Select` input, which allows users to filter results as they type. It supports all of the common input props, with the main difference being that the `options` prop is supplied as data (or a function), rather than nested `children`.
|
|
10
|
+
|
|
11
|
+
`Combobox` is fully accessible, and supports keyboard navigation and screen readers.
|
|
12
|
+
|
|
13
|
+
### Static / synchronous use
|
|
14
|
+
|
|
15
|
+
When you know all of the options data upfront, you can provide it as either:
|
|
16
|
+
|
|
17
|
+
- A flat array of strings
|
|
18
|
+
- An array of objects with type `Option` (see below)
|
|
19
|
+
- An array of groups of Options, with headings
|
|
20
|
+
|
|
21
|
+
The default `onChange` behaviour, while typing, is to filter the options list based on input value.
|
|
22
|
+
|
|
23
|
+
The `onSelected` prop is called when an option is selected, passing `Option` as its parameter.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
type Option = {
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
value?: string | number | boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type OptionGroup = {
|
|
33
|
+
category: string;
|
|
34
|
+
options: Option[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type Options = (string | Option | OptionGroup)[];
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Dynamic / asynchronous use
|
|
41
|
+
|
|
42
|
+
`Combobox` provides a useful primitive with which to build more dynamic selector components, such as `Trade Selector` and `Adddress Lookup`.
|
|
43
|
+
|
|
44
|
+
For these kinds of component, options (list) data is not known and must be fetched, typically from an API, as the user types. In this case, the `options` prop can be overloaded with a function that returns a promise.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
type OptionsFetcher = (inputValue: string) => Promise<Options>;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
In asynchronous mode, you are strongly encouraged to provide a `delay` value in milliseconds, to debounce API requests.
|
|
51
|
+
|
|
52
|
+
### Overriding selection behaviour
|
|
53
|
+
|
|
54
|
+
While is it often sufficient to fetch data, present it in the list and use an `onSelected` handler to do something with the chosen option, there are times when you may want to override the default behaviour. E.g. Address Lookup can return results that are not full addresses, but are groups, which need further drill down.
|
|
55
|
+
|
|
56
|
+
In these cases, you can enhance the `onSelected` handler with a `callback` function. If a selected option has a callback, then it will be invoked, passing the option as params.
|
|
57
|
+
|
|
58
|
+
When a selected option does not have a callback, the standard handler is invoked.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
yarn add @simplybusiness/mobius
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import { ComboboxStories } from "@simplybusiness/mobius";
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Examples
|
|
73
|
+
|
|
74
|
+
### Default
|
|
75
|
+
|
|
76
|
+
<Story of={ComboboxStories.Default} />
|
|
77
|
+
|
|
78
|
+
{/* prettier-ignore */}
|
|
79
|
+
```jsx
|
|
80
|
+
import { Combobox } from "@simplybusiness/mobius";
|
|
81
|
+
|
|
82
|
+
<Combobox options={["Apple", "Apricot", "etc."]} />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### With an options object
|
|
86
|
+
|
|
87
|
+
<Story of={ComboboxStories.OptionsObject} />
|
|
88
|
+
|
|
89
|
+
{/* prettier-ignore */}
|
|
90
|
+
```jsx
|
|
91
|
+
import { Combobox } from "@simplybusiness/mobius";
|
|
92
|
+
|
|
93
|
+
<Combobox options={[
|
|
94
|
+
{ id: "apple", label: "Apple" },
|
|
95
|
+
{ id: "apricot", label: "Apricot" },
|
|
96
|
+
{ id: "etc", label: "etc." }
|
|
97
|
+
]} />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### With Icon
|
|
101
|
+
|
|
102
|
+
<Story of={ComboboxStories.WithIcon} />
|
|
103
|
+
|
|
104
|
+
{/* prettier-ignore */}
|
|
105
|
+
```jsx
|
|
106
|
+
import { Combobox, Icon } from "@simplybusiness/mobius";
|
|
107
|
+
import { search } from "@simplybusiness/icons";
|
|
108
|
+
|
|
109
|
+
<Combobox
|
|
110
|
+
options={[...OPTIONS]}
|
|
111
|
+
icon={<Icon icon={search}/>}
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### State Selector
|
|
116
|
+
|
|
117
|
+
This is a simple example of using `Combobox` to create a US State Selector, which uses a flat list of state strings as the options array.
|
|
118
|
+
|
|
119
|
+
<Story of={ComboboxStories.StateSelector} />
|
|
120
|
+
|
|
121
|
+
{/* prettier-ignore */}
|
|
122
|
+
```jsx
|
|
123
|
+
import { Combobox, Icon } from "@simplybusiness/mobius";
|
|
124
|
+
import { search } from "@simplybusiness/icons";
|
|
125
|
+
|
|
126
|
+
<Combobox
|
|
127
|
+
options={[...US_STATES]}
|
|
128
|
+
placeholder="Choose your state"
|
|
129
|
+
icon={<Icon icon={search}/>}
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Accessibility
|
|
134
|
+
|
|
135
|
+
TODO: Add accessibility information
|
|
136
|
+
|
|
137
|
+
## Props
|
|
138
|
+
|
|
139
|
+
<ArgTypes of={Combobox} />
|
|
140
|
+
|
|
141
|
+
## Component HTML Structure and Class names
|
|
142
|
+
|
|
143
|
+
The following HTML is rendered for a Combobox:
|
|
144
|
+
|
|
145
|
+
```html
|
|
146
|
+
<label class="mobius-label" htmlFor="{input-id}">{Label text}</label>
|
|
147
|
+
```
|
|
@@ -1,26 +1,71 @@
|
|
|
1
|
+
import { search } from "@simplybusiness/icons";
|
|
1
2
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
3
|
import { Combobox, type ComboboxProps } from ".";
|
|
3
|
-
import {
|
|
4
|
+
import { excludeControls } from "../../utils";
|
|
5
|
+
import { StoryContainer } from "../../utils/StoryContainer";
|
|
6
|
+
import { Icon } from "../Icon";
|
|
7
|
+
import { FRUITS, FRUITS_OBJECTS, FRUITS_GROUPS, US_STATES } from "./fixtures";
|
|
4
8
|
|
|
5
9
|
type StoryType = StoryObj<typeof Combobox>;
|
|
6
10
|
|
|
7
11
|
const meta: Meta<typeof Combobox> = {
|
|
8
12
|
title: "Forms/Combobox",
|
|
9
13
|
component: Combobox,
|
|
14
|
+
argTypes: {
|
|
15
|
+
type: {
|
|
16
|
+
options: [],
|
|
17
|
+
},
|
|
18
|
+
...excludeControls("icon"), // options
|
|
19
|
+
},
|
|
20
|
+
decorators: [
|
|
21
|
+
Story => (
|
|
22
|
+
<StoryContainer>
|
|
23
|
+
<Story />
|
|
24
|
+
</StoryContainer>
|
|
25
|
+
),
|
|
26
|
+
],
|
|
10
27
|
};
|
|
11
28
|
|
|
12
29
|
export const Default: StoryType = {
|
|
13
30
|
render: (args: ComboboxProps) => <Combobox {...args} />,
|
|
14
31
|
args: {
|
|
32
|
+
label: "Fruits",
|
|
15
33
|
options: FRUITS,
|
|
16
34
|
},
|
|
17
35
|
};
|
|
18
36
|
|
|
19
|
-
export const
|
|
37
|
+
export const OptionsObject: StoryType = {
|
|
20
38
|
render: (args: ComboboxProps) => <Combobox {...args} />,
|
|
21
39
|
args: {
|
|
40
|
+
label: "Fruits",
|
|
22
41
|
options: FRUITS_OBJECTS,
|
|
23
42
|
},
|
|
24
43
|
};
|
|
25
44
|
|
|
45
|
+
export const GroupedOptions: StoryType = {
|
|
46
|
+
render: (args: ComboboxProps) => <Combobox {...args} />,
|
|
47
|
+
args: {
|
|
48
|
+
options: FRUITS_GROUPS,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const WithIcon: StoryType = {
|
|
53
|
+
render: (args: ComboboxProps) => <Combobox {...args} />,
|
|
54
|
+
args: {
|
|
55
|
+
label: "Fruits",
|
|
56
|
+
icon: <Icon icon={search} />,
|
|
57
|
+
options: FRUITS_OBJECTS,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const StateSelector: StoryType = {
|
|
62
|
+
render: (args: ComboboxProps) => <Combobox {...args} />,
|
|
63
|
+
args: {
|
|
64
|
+
label: "US State Selector",
|
|
65
|
+
placeholder: "Choose your state",
|
|
66
|
+
icon: <Icon icon={search} />,
|
|
67
|
+
options: US_STATES,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
26
71
|
export default meta;
|