Skip to content

Commit

Permalink
Merge pull request #1725 from okta/ee/autocomplete
Browse files Browse the repository at this point in the history
Style Autocomplete
  • Loading branch information
edburyenegren-okta authored Apr 20, 2023
2 parents ab93c43 + 26d732d commit 7f35e7f
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 23 deletions.
132 changes: 132 additions & 0 deletions packages/odyssey-react-mui/src/Autocomplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*!
* Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*/

import {
Autocomplete as MuiAutocomplete,
AutocompleteProps as MuiAutocompleteProps,
InputBase,
} from "@mui/material";
import { memo, useCallback } from "react";

import { Field } from "./Field";

export type AutocompleteProps<
OptionType,
HasMultipleChoices extends boolean | undefined,
IsCustomValueAllowed extends boolean | undefined
> = {
hasMultipleChoices?: MuiAutocompleteProps<
OptionType,
HasMultipleChoices,
undefined,
IsCustomValueAllowed
>["multiple"];
hint?: string;
isCustomValueAllowed?: MuiAutocompleteProps<
OptionType,
HasMultipleChoices,
undefined,
IsCustomValueAllowed
>["freeSolo"];
isDisabled?: MuiAutocompleteProps<
OptionType,
HasMultipleChoices,
undefined,
IsCustomValueAllowed
>["disabled"];
isLoading?: MuiAutocompleteProps<
OptionType,
HasMultipleChoices,
undefined,
IsCustomValueAllowed
>["loading"];
isReadOnly?: MuiAutocompleteProps<
OptionType,
HasMultipleChoices,
undefined,
IsCustomValueAllowed
>["readOnly"];
label: string;
onChange?: MuiAutocompleteProps<
OptionType,
HasMultipleChoices,
undefined,
IsCustomValueAllowed
>["onChange"];
options: MuiAutocompleteProps<
OptionType,
HasMultipleChoices,
undefined,
IsCustomValueAllowed
>["options"];
value?: MuiAutocompleteProps<
OptionType,
HasMultipleChoices,
undefined,
IsCustomValueAllowed
>["value"];
};

const Autocomplete = <
OptionType,
HasMultipleChoices extends boolean | undefined,
IsCustomValueAllowed extends boolean | undefined
>({
isCustomValueAllowed,
hasMultipleChoices,
isDisabled,
isLoading,
isReadOnly,
hint,
label,
onChange,
options,
value,
}: AutocompleteProps<OptionType, HasMultipleChoices, IsCustomValueAllowed>) => {
const renderInput = useCallback(
({ InputLabelProps, InputProps, ...params }) => (
<Field
{...InputLabelProps}
hasVisibleLabel
hint={hint}
label={label}
renderFieldComponent={({ ariaDescribedBy, id }) => (
<InputBase
{...params}
{...InputProps}
aria-describedby={ariaDescribedBy}
id={id}
/>
)}
/>
),
[hint, label]
);

return (
<MuiAutocomplete
disabled={isDisabled}
freeSolo={isCustomValueAllowed}
loading={isLoading}
multiple={hasMultipleChoices}
onChange={onChange}
options={options}
readOnly={isReadOnly}
renderInput={renderInput}
value={value}
/>
);
};

const MemoizedAutocomplete = memo(Autocomplete) as typeof Autocomplete;

export { MemoizedAutocomplete as Autocomplete };
1 change: 0 additions & 1 deletion packages/odyssey-react-mui/src/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,5 @@ const MenuItem = forwardRef<HTMLLIElement, MenuItemProps>(
);

const MemoizedMenuItem = memo(MenuItem);
MemoizedMenuItem.displayName = "MenuItem";

export { MemoizedMenuItem as MenuItem };
1 change: 1 addition & 0 deletions packages/odyssey-react-mui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export { default as FavoriteIcon } from "@mui/icons-material/Favorite";

export { deepmerge, visuallyHidden } from "@mui/utils";

export * from "./Autocomplete";
export * from "./Banner";
export * from "./Checkbox";
export * from "./CheckboxGroup";
Expand Down
101 changes: 85 additions & 16 deletions packages/odyssey-react-mui/src/theme/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
CheckCircleFilledIcon,
ChevronDownIcon,
CloseCircleFilledIcon,
CloseIcon,
InformationCircleFilledIcon,
SubtractIcon,
} from "../iconDictionary";
Expand Down Expand Up @@ -144,6 +145,68 @@ export const components: ThemeOptions["components"] = {
}),
},
},
MuiAutocomplete: {
defaultProps: {
autoHighlight: true,
autoSelect: false,
blurOnSelect: false,
clearIcon: <CloseIcon />,
clearOnEscape: true,
disableClearable: false,
disabledItemsFocusable: false,
disableListWrap: false,
disablePortal: false,
filterSelectedOptions: false,
fullWidth: false,
handleHomeEndKeys: true,
includeInputInList: true,
limitTags: -1,
openOnFocus: false,
popupIcon: <ChevronDownIcon />,
selectOnFocus: true,
},
styleOverrides: {
clearIndicator: ({ theme }) => ({
marginRight: "unset",
padding: theme.spacing(1),
}),
endAdornment: ({ theme, ownerState }) => ({
display: "flex",
gap: theme.spacing(1),
top: `calc(${theme.spacing(2)} - ${theme.mixins.borderWidth})`,
right: theme.spacing(2),
maxHeight: "unset",
alignItems: "center",
whiteSpace: "nowrap",
color: theme.palette.action.active,

...(ownerState.disabled === true && {
display: "none",
}),

...(ownerState.readOnly === true && {
display: "none",
}),
}),
loading: ({ theme }) => ({
paddingBlock: theme.spacing(3),
paddingInline: theme.spacing(4),
}),
popupIndicator: ({ theme }) => ({
padding: theme.spacing(1),
marginRight: "unset",
}),
inputRoot: ({ theme, ownerState }) => ({
...(ownerState.readOnly === true && {
backgroundColor: theme.palette.grey[50],

[`&:not(:hover)`]: {
borderColor: "transparent",
},
}),
}),
},
},
MuiBackdrop: {
styleOverrides: {
root: ({ ownerState }) => ({
Expand Down Expand Up @@ -468,19 +531,6 @@ export const components: ThemeOptions["components"] = {
paddingInlineEnd: theme.spacing(2),
}),

[`& .${chipClasses.deleteIcon}`]: {
WebkitTapHighlightColor: "transparent",
color: theme.palette.text.secondary,
fontSize: "1em",
cursor: "pointer",
margin: "0",
marginInlineStart: theme.spacing(2),

"&:hover": {
color: theme.palette.text.primary,
},
},

[`&.${chipClasses.disabled}`]: {
opacity: 1,
pointerEvents: "none",
Expand Down Expand Up @@ -539,10 +589,32 @@ export const components: ThemeOptions["components"] = {
},
},
}),

[`.${inputBaseClasses.root}.${inputBaseClasses.disabled} &`]: {
backgroundColor: theme.palette.grey[200],
},
}),

label: {
padding: 0,
},

deleteIcon: ({ theme }) => ({
WebkitTapHighlightColor: "transparent",
color: theme.palette.text.secondary,
fontSize: "1em",
cursor: "pointer",
margin: "0",
marginInlineStart: theme.spacing(2),

"&:hover": {
color: theme.palette.text.primary,
},

[`.${inputBaseClasses.root}.${inputBaseClasses.disabled} &`]: {
display: "none",
},
}),
},
},
MuiCircularProgress: {
Expand Down Expand Up @@ -1112,9 +1184,6 @@ export const components: ThemeOptions["components"] = {
},
},
MuiInputAdornment: {
defaultProps: {
variant: "outlined",
},
styleOverrides: {
root: ({ theme, ownerState }) => ({
display: "flex",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { ArgsTable, Canvas, Meta, Story } from "@storybook/addon-docs";

<Meta anchor />

# Autocomplete

Similar to <a href="?path=/docs/mui-components-forms-select--default">Select</a>, this input triggers a menu of options a user can select. Country and state Autocompletes are common examples.

<Canvas>
<Story id="mui-components-forms-autocomplete--default" />
</Canvas>

## Behavior

Interacting with an Autocomplete displays a list of values to choose from. Users may filter the options list by typing.

## Variants

Odyssey provides support for both single and multi-value Autocompletes.

### Single-select

With the single-select variant, choosing a value will override any previous selection and close the Autocomplete.

#### Enabled

<Canvas>
<Story id="mui-components-forms-autocomplete--default" />
</Canvas>

#### Disabled

<Canvas>
<Story id="mui-components-forms-autocomplete--disabled" />
</Canvas>

#### Read-only

<Canvas>
<Story id="mui-components-forms-autocomplete--read-only" />
</Canvas>

### Multi-Select

The multi-Select variant allows users to select many values.

#### Enabled

<Canvas>
<Story id="mui-components-forms-autocomplete--multiple" />
</Canvas>

#### Disabled

<Canvas>
<Story id="mui-components-forms-autocomplete--multiple-disabled" />
</Canvas>

#### Read-only

<Canvas>
<Story id="mui-components-forms-autocomplete--multiple-read-only" />
</Canvas>

## Loading state

The loading state is displayed when retrieving values from the server or when data is unavailable.

<Canvas>
<Story id="mui-components-forms-autocomplete--loading" />
</Canvas>

## Custom Values

Autocomplete also supports user-submitted values via the `isCustomValueAllowed` prop.

<Canvas>
<Story id="mui-components-forms-autocomplete--is-custom-value-allowed" />
</Canvas>
Loading

0 comments on commit 7f35e7f

Please sign in to comment.