Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Style Autocomplete #1725

Merged
merged 26 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
595efa6
chore: example of broken Autocomplete behavior
edburyenegren-okta Mar 27, 2023
5e632db
chore: label examples in doc
edburyenegren-okta Mar 27, 2023
5b345e3
chore: set up working examples for all states
edburyenegren-okta Mar 27, 2023
c994737
chore: set default props for Autocomplete
edburyenegren-okta Mar 28, 2023
cd297cb
chore: remove defaults from stories
edburyenegren-okta Mar 29, 2023
ff432c0
Merge branch 'develop' into ee/autocomplete
KevinGhadyani-Okta Apr 17, 2023
dbb1210
fix: created Autocomplete wrapper component
KevinGhadyani-Okta Apr 17, 2023
28be391
fix: export types for Autocomplete
KevinGhadyani-Okta Apr 17, 2023
a93542c
fix: separated InputProps and params in Autocomplete
KevinGhadyani-Okta Apr 17, 2023
d7aff4a
fix: added extra props to Autocomplete to fix MUI types
KevinGhadyani-Okta Apr 17, 2023
ab84734
fix: removed explicit displayName from MenuItem
KevinGhadyani-Okta Apr 18, 2023
8d28a8c
fix: types for Autocomplete in stories
KevinGhadyani-Okta Apr 18, 2023
66e7710
chore: remove unnec ComponentStory
edburyenegren-okta Apr 18, 2023
79d42e1
fix: minor type improvement in Autocomplete stories
KevinGhadyani-Okta Apr 18, 2023
2a3c46a
Merge remote-tracking branch 'origin/ee/autocomplete' into ee/autocom…
KevinGhadyani-Okta Apr 18, 2023
0f32442
chore: update stories to match new props
edburyenegren-okta Apr 18, 2023
f2da4da
chore: remove MUI-only stories
edburyenegren-okta Apr 18, 2023
eaef418
chore: remove mui-only exports
edburyenegren-okta Apr 18, 2023
b03516b
feat(odyssey-react-mui): add styling for Autocomplete
edburyenegren-okta Apr 18, 2023
42f6c03
chore: enable and styling loading state
edburyenegren-okta Apr 18, 2023
5397b59
chore: add and style isReadOnly state
edburyenegren-okta Apr 18, 2023
5923b59
chore: fix border color hover behavior
edburyenegren-okta Apr 18, 2023
0254034
chore: remove unnec autocomplete classes
edburyenegren-okta Apr 18, 2023
7680cfe
chore: add readonly and disabled examples
edburyenegren-okta Apr 18, 2023
72c40f1
chore: tune disabled states for Autocomplete tags
edburyenegren-okta Apr 19, 2023
26d732d
docs(odyssey-storybook): update Select and Autocomplete stories
edburyenegren-okta Apr 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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],
},
Comment on lines +593 to +595
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming we won't have to use grey[200] with Design Tokens v2 right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. We don't have a token for it currently, but I'll be doing replacements with the new schema.

}),

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