diff --git a/packages/components/src/core/Autocomplete/__snapshots__/index.test.tsx.snap b/packages/components/src/core/Autocomplete/__snapshots__/index.test.tsx.snap index 19b7f858a..c66737465 100644 --- a/packages/components/src/core/Autocomplete/__snapshots__/index.test.tsx.snap +++ b/packages/components/src/core/Autocomplete/__snapshots__/index.test.tsx.snap @@ -5,7 +5,7 @@ exports[` Default story renders snapshot 1`] = ` style="margin: 16px 0px 0px 24px; width: 300px;" >
{ + return ( + + ); + }, []); + return ( { - const { search, hasSections } = props; + const { search } = props; const spacings = getSpaces(props); const colors = getColors(props); const borders = getBorders(props); @@ -55,60 +53,56 @@ export const StyledAutocomplete = styled(Autocomplete, { } & + .MuiAutocomplete-popper > .MuiAutocomplete-paper { - - ${ - search || hasSections - ? `padding-left: ${spacings?.s}px !important;` - : "" - } - - .MuiAutocomplete-listbox { - max-height: 40vh; - padding-top: 0; - padding-bottom: 0; - padding-right: ${spacings?.s}px; - - .MuiAutocomplete-option { - min-height: unset; - } - - .MuiAutocomplete-option.Mui-focused { - background-color: ${colors?.gray[100]}; - } - - .MuiAutocomplete-option[aria-selected="true"] { - background-color: white; + ${search ? `padding-left: ${spacings?.s}px !important;` : ""} + + .MuiAutocomplete-listbox { + max-height: 40vh; + padding-top: 0; + padding-bottom: 0; + padding-right: ${spacings?.s}px; + + .MuiAutocomplete-option { + min-height: unset; + } + + .MuiAutocomplete-option.Mui-focused { + background-color: ${colors?.gray[100]}; + } + + .MuiAutocomplete-option[aria-selected="true"] { + background-color: white; + } + + .MuiAutocomplete-option[aria-disabled="true"] { + opacity: 1; + } + + .MuiAutocomplete-option[aria-selected="true"].Mui-focused { + background-color: ${colors?.gray[100]}; + } + + & > li:last-child .MuiAutocomplete-groupUl { + border-bottom: none; + margin-bottom: 0; + } } - .MuiAutocomplete-option[aria-disabled="true"] { - opacity: 1; + .MuiAutocomplete-groupLabel { + top: 0; + color: ${colors?.gray[500]}; + padding: ${spacings?.xxs}px 0 ${spacings?.xxs}px 0; } - .MuiAutocomplete-option[aria-selected="true"].Mui-focused { - background-color: ${colors?.gray[100]}; - } - - & > li:last-child .MuiAutocomplete-groupUl { - border-bottom: none; - margin-bottom: 0; - } - } - - .MuiAutocomplete-groupLabel { - top: 0; - color: ${colors?.gray[500]}; - padding: ${spacings?.xxs}px 0 ${spacings?.xxs}px 0; - } - - .MuiAutocomplete-groupUl { - margin-bottom: ${spacings?.m}px; - position: relative; - padding: 0 0 ${spacings?.xs}px 0 0; - border-bottom: ${borders?.gray[200]}; - - & li:last-of-type { + .MuiAutocomplete-groupUl { + margin-bottom: ${spacings?.m}px; position: relative; - margin-bottom: ${spacings?.xxs}px; + padding: 0 0 ${spacings?.xs}px 0 0; + border-bottom: ${borders?.gray[200]}; + + & li:last-of-type { + position: relative; + margin-bottom: ${spacings?.xxs}px; + } } } `; diff --git a/packages/components/src/core/Dropdown/index.tsx b/packages/components/src/core/Dropdown/index.tsx index 01c0ff023..5f93bd7c1 100644 --- a/packages/components/src/core/Dropdown/index.tsx +++ b/packages/components/src/core/Dropdown/index.tsx @@ -111,13 +111,13 @@ const Dropdown = ({ useEffect(() => { onChange(value); - }, [value]); + }, [onChange, value]); useEffect(() => { if (isControlled) { setValue(propValue); } - }, [propValue]); + }, [isControlled, propValue]); const [open, setOpen] = useState(false); diff --git a/packages/components/src/core/DropdownMenu/index.stories.tsx b/packages/components/src/core/DropdownMenu/index.stories.tsx index dd7dc6241..f9c9781d3 100644 --- a/packages/components/src/core/DropdownMenu/index.stories.tsx +++ b/packages/components/src/core/DropdownMenu/index.stories.tsx @@ -284,7 +284,6 @@ const LivePreviewDemo = (): JSX.Element => { }} search={false} multiple={false} - hasSections={false} value={value1} onClickAway={handleClickAway1} /> @@ -369,7 +368,6 @@ const LivePreviewDemo = (): JSX.Element => { open={!!open4} search={false} multiple - hasSections groupBy={(option) => option.section as string} onChange={handleChange4} disableCloseOnSelect @@ -638,7 +636,6 @@ const ScreenshotTestDemo = (props: Args): JSX.Element => {

diff --git a/packages/components/src/core/DropdownMenu/index.tsx b/packages/components/src/core/DropdownMenu/index.tsx index ae9b3d0aa..c139e6540 100644 --- a/packages/components/src/core/DropdownMenu/index.tsx +++ b/packages/components/src/core/DropdownMenu/index.tsx @@ -1,53 +1,24 @@ import { - AutocompleteFreeSoloValueMapping, AutocompleteInputChangeReason, AutocompleteProps, AutocompleteRenderInputParams, - AutocompleteRenderOptionState, ClickAwayListener, - InputAdornment, ClickAwayListenerProps as MUIClickAwayListenerProps, + PaperProps, PopperProps, } from "@mui/material"; -import React, { ReactNode, SyntheticEvent, useState } from "react"; -import { noop } from "src/common/utils"; -import ButtonIcon from "../ButtonIcon"; -import { IconProps } from "../Icon"; +import React, { SyntheticEvent, useCallback } from "react"; +import Autocomplete, { DefaultAutocompleteOption } from "../Autocomplete"; import { InputSearchProps } from "../InputSearch"; -import MenuItem, { IconNameToSmallSizes } from "../MenuItem"; import { - InputBaseWrapper, StyleProps, - StyledAutocomplete, + StyledAutocompletePopper, StyledHeaderTitle, - StyledMenuInputSearch, - StyledMenuItemDetails, - StyledMenuItemText, StyledPaper, StyledPopper, } from "./style"; -// (thuang): This requires option to have a `name` property. -interface DropdownMenuOptionGeneral { - name: string; - section?: string; -} -export interface DropdownMenuOptionBasic extends DropdownMenuOptionGeneral { - count?: number; - details?: string; - sdsIcon?: keyof IconNameToSmallSizes; - sdsIconProps?: Partial>; -} - -export interface DropdownMenuOptionComponent extends DropdownMenuOptionGeneral { - component?: ReactNode; -} - -type Exclusive = T & { [K in Exclude]?: undefined }; - -export type DefaultDropdownMenuOption = - | Exclusive - | Exclusive; +export type DefaultDropdownMenuOption = DefaultAutocompleteOption; // eslint-disable-next-line @typescript-eslint/no-explicit-any type RenderFunctionType = (props: any) => JSX.Element; @@ -63,8 +34,10 @@ interface ExtraDropdownMenuProps extends StyleProps { InputBaseProps?: Partial; PopperBaseProps?: Partial; title?: string; + label?: string; anchorEl: HTMLElement | null; PopperComponent?: typeof StyledPopper | RenderFunctionType; + PopperPlacement?: "bottom-start" | "top-start" | "bottom-end" | "top-end"; PaperComponent?: typeof StyledPaper | RenderFunctionType; children?: JSX.Element | null; onClickAway: (event: MouseEvent | TouchEvent) => void; @@ -98,32 +71,31 @@ const DropdownMenu = < props: DropdownMenuProps ): JSX.Element => { const { - multiple = false, anchorEl, - disableCloseOnSelect = multiple, - getOptionLabel = defaultGetOptionLabel, id, - InputBaseProps = {}, - isOptionEqualToValue = defaultIsOptionEqualToValue, - keepSearchOnSelect = true, - loading = false, - loadingText = "", - noOptionsText = "No options", - onInputChange = noop, + InputBaseProps = { + autoFocus: true, + sx: { + padding: "8px", + }, + }, open = false, PaperComponent = StyledPaper, PopperComponent = StyledPopper, + PopperPlacement = "bottom-start", PopperBaseProps = {}, - renderOption = defaultRenderOption, - renderTags = defaultRenderTags, search = false, title, + label = "Search", children, onClickAway, ClickAwayListenerProps, + ...rest } = props; - const [inputValue, setInputValue] = useState(""); + const defaultPopperComponent = useCallback((popperProps: PopperProps) => { + return ; + }, []); return (
@@ -147,139 +119,29 @@ const DropdownMenu = < {title} )} - ( - - ( + { - if (event.key === "Backspace") { - event.stopPropagation(); - } - }} - InputProps={{ - /** - * (thuang): Works with css caret-color: "transparent" to hide - * mobile keyboard - */ - inputMode: search ? "text" : "none", - /** - * (mmoore): passing only the ref along to InputProps to prevent - * default MUI arrow from rendering in search input. - * renderInput strips InputProps, so we explicitly pass end adornment here - */ - ...params.InputProps.ref, - endAdornment: ( - - - - ), - inputProps: params.inputProps, - }} - {...InputBaseProps} + title={title} + {...paperComponentProps} /> - + ), + [PaperComponent, search, title] )} - {...props} - onInputChange={( - event: SyntheticEvent, - value: string, - reason: AutocompleteInputChangeReason - ) => { - if (event && event.type === "blur") { - setInputValue(""); - } else if ( - reason !== "reset" || - (reason === "reset" && !keepSearchOnSelect) - ) { - setInputValue(value); - } - if (onInputChange) onInputChange(event, value, reason); - }} + open={open} + {...rest} + PopperComponent={defaultPopperComponent} /> {children}
); - - function defaultGetOptionLabel( - option: T | AutocompleteFreeSoloValueMapping - ): string { - if (typeof option === "object" && "name" in option) return option.name; - return option.toString(); - } - - function defaultIsOptionEqualToValue(option: T, val: T): boolean { - return option.name === val.name; - } - - function defaultRenderTags() { - return null; - } - - function defaultRenderOption( - optionProps: React.HTMLAttributes, - option: T, - { selected }: AutocompleteRenderOptionState - ) { - let MenuItemContent; - - const { component, details, count, sdsIcon, sdsIconProps } = option; - const label = getOptionLabel(option); - - if (component) { - MenuItemContent = component; - } else { - MenuItemContent = ( - - {label} - {details && ( - - {details} - - )} - - ); - } - - return ( - - {MenuItemContent} - - ); - } }; export default DropdownMenu; diff --git a/packages/components/src/core/DropdownMenu/style.ts b/packages/components/src/core/DropdownMenu/style.ts index f3504f3f6..53976a029 100644 --- a/packages/components/src/core/DropdownMenu/style.ts +++ b/packages/components/src/core/DropdownMenu/style.ts @@ -1,14 +1,10 @@ -import { Autocomplete, Paper, Popper } from "@mui/material"; +import { Paper, Popper } from "@mui/material"; import { styled } from "@mui/material/styles"; import { ReactElement } from "react"; -import InputSearch from "../InputSearch"; import { CommonThemeProps, - fontBodyXxs, - fontCapsXxxxs, fontHeaderXs, getBorders, - getColors, getCorners, getShadows, getSpaces, @@ -17,7 +13,6 @@ import { export interface StyleProps extends CommonThemeProps { count?: number; - hasSections?: boolean; icon?: ReactElement; search?: boolean; title?: string; @@ -29,152 +24,22 @@ const doNotForwardProps = [ "keepSearchOnSelect", "search", "InputBaseProps", - "hasSections", "title", "PopperBaseProps", "onClickAway", "ClickAwayListenerProps", ]; -export const StyledAutocomplete = styled(Autocomplete, { - shouldForwardProp: (prop: string) => !doNotForwardProps.includes(prop), -})` - + .MuiAutocomplete-popper - > .MuiAutocomplete-paper - .MuiAutocomplete-groupLabel { - ${fontCapsXxxxs} - } - - ${(props: StyleProps) => { - const { search, title, hasSections } = props; - const spacings = getSpaces(props); - const colors = getColors(props); - const borders = getBorders(props); - - return ` - ${!search && `height: 0`}; - - .MuiOutlinedInput-root.MuiInputBase-formControl.MuiInputBase-adornedEnd { - padding: 0 ${spacings?.l}px 0 0; - - .MuiAutocomplete-input { - padding: ${spacings?.xs}px ${spacings?.l}px; - } - } - - & + .MuiAutocomplete-popper { - position: relative !important; - transform: none !important; - width: 100% !important; - } - - & + .MuiAutocomplete-popper > .MuiAutocomplete-paper { - - ${ - search || title || hasSections - ? `padding-left: ${spacings?.s}px !important;` - : "" - } +// export const StyledAutocomplete = styled(Autocomplete, { +// shouldForwardProp: (prop: string) => !doNotForwardProps.includes(prop), +// })` +// ${(props: StyleProps) => { +// const spacings = getSpaces(props); - .MuiAutocomplete-listbox { - max-height: 40vh; - padding-top: 0; - padding-bottom: 0; - - ${ - search || title || hasSections - ? `padding-right: ${spacings?.s}px;` - : "" - } - - .MuiAutocomplete-option { - min-height: unset; - } - - .MuiAutocomplete-option.Mui-focused { - background-color: ${colors?.gray[100]}; - } - - .MuiAutocomplete-option[aria-selected="true"] { - background-color: white; - } - - .MuiAutocomplete-option[aria-disabled="true"] { - opacity: 1; - } - - .MuiAutocomplete-option[aria-selected="true"].Mui-focused { - background-color: ${colors?.gray[100]}; - } - - & > li:last-child .MuiAutocomplete-groupUl { - border-bottom: none; - margin-bottom: 0; - } - } - - .MuiAutocomplete-groupLabel { - top: 0; - color: ${colors?.gray[500]}; - padding: ${spacings?.xxs}px 0 ${spacings?.xxs}px 0; - } - - .MuiAutocomplete-groupUl { - margin-bottom: ${spacings?.m}px; - position: relative; - padding: 0 0 ${spacings?.xs}px 0 0; - border-bottom: ${borders?.gray[200]}; - - & li:last-of-type { - position: relative; - margin-bottom: ${spacings?.xxs}px; - } - } - `; - }} -` as typeof Autocomplete; - -export const InputBaseWrapper = styled("div", { - shouldForwardProp: (prop: string) => !doNotForwardProps.includes(prop), -})` - ${(props: StyleProps) => { - const { search } = props; - - if (!search) { - // (thuang): We cannot use `display: none;` here, since - // the component needs to be in the DOM to handle backdrop - // click to close the menu - return ` - border: 0; - padding: 0; - - white-space: nowrap; - - clip-path: inset(100%); - clip: rect(0 0 0 0); - overflow: hidden; - margin: 0; - `; - } - - const spacings = getSpaces(props); - - return ` - margin: ${spacings?.s}px; - `; - }} -`; - -export const StyledMenuInputSearch = styled(InputSearch, { - shouldForwardProp: (prop: string) => !doNotForwardProps.includes(prop), -})<{ search: boolean }>` - margin: 0; - .MuiInputBase-root { - width: 100%; - } - /* (thuang): Works with attribute inputMode: "none" to hide mobile keyboard */ - caret-color: ${({ search }) => (search ? "auto" : "transparent")}; -`; +// return ` +// `; +// }} +// ` as typeof Autocomplete; export const StyledHeaderTitle = styled("div", { shouldForwardProp: (prop: string) => !doNotForwardProps.includes(prop), @@ -228,27 +93,29 @@ export const StyledPopper = styled(Popper, { }} `; -export const StyledPaper = styled(Paper)` - box-shadow: none; - margin: 0; - border-radius: 0; - padding-top: 0; - padding-bottom: 0; -`; - -export const StyledMenuItemDetails = styled("div")` - ${fontBodyXxs} - ${(props) => { - const colors = getColors(props); +export const StyledPaper = styled(Paper, { + shouldForwardProp: (prop: string) => !doNotForwardProps.includes(prop), +})` + ${(props: StyleProps) => { + const { title } = props; + const spacings = getSpaces(props); return ` - color: ${colors?.gray[500]}; - white-space: pre-wrap; + box-shadow: none; + margin: 0; + border-radius: 0; + padding-top: 0; + padding-bottom: 0; + ${title ? `padding-left: ${spacings?.s}px !important;` : ``} `; }} `; -export const StyledMenuItemText = styled("div")` - display: flex; - flex-direction: column; +export const StyledAutocompletePopper = styled(Popper)` + position: relative !important; + transform: none !important; + width: 100% !important; + box-shadow: none; + padding: 0; + border: none; `;