From f5e6032b8c9a31696daf97266ae26ca5a8d6be5c Mon Sep 17 00:00:00 2001 From: Zoe Hayes Date: Thu, 9 May 2024 16:20:42 -0400 Subject: [PATCH] fix: correct QueryItem Cascader and Action cosmetic bugs/flaws --- .../data-entry/QueryItem/Action.tsx | 3 +- .../data-entry/QueryItem/Cascader.tsx | 88 +++++++++++++++---- .../data-entry/QueryItem/query-item.css | 16 +++- 3 files changed, 86 insertions(+), 21 deletions(-) diff --git a/src/components/data-entry/QueryItem/Action.tsx b/src/components/data-entry/QueryItem/Action.tsx index a445ad163..16c2393f2 100644 --- a/src/components/data-entry/QueryItem/Action.tsx +++ b/src/components/data-entry/QueryItem/Action.tsx @@ -9,7 +9,8 @@ export interface IActionProps { export const Action = (props: IActionProps) => { let buttonClassNames: string = 'query-item query-item--action' - if ((props.type ?? 'default') !== 'primary') buttonClassNames += ` query-item--secondary` + if ((props.type ?? 'default') === 'default') buttonClassNames += ` query-item--secondary` + if ((props.type ?? 'default') === 'disabled') buttonClassNames += ` query-item--disabled` const baseProps: IButtonProps = { className: buttonClassNames, diff --git a/src/components/data-entry/QueryItem/Cascader.tsx b/src/components/data-entry/QueryItem/Cascader.tsx index 43d55090b..36c340185 100644 --- a/src/components/data-entry/QueryItem/Cascader.tsx +++ b/src/components/data-entry/QueryItem/Cascader.tsx @@ -1,6 +1,6 @@ import './query-item.css' import { GetProp } from 'antd' -import { useCallback, useEffect, useState } from 'react' +import { ReactNode, useCallback, useEffect, useState } from 'react' import { Cascader as BaseCascader, Flex, @@ -21,7 +21,7 @@ export interface ICascaderOption { export interface ICascaderProps { options: ICascaderOption[] - icon?: keyof Pick + icon?: keyof Pick errorMessage?: string placeholder?: string onChange?: (values: (number | string)[], selectedOptions: any) => Promise @@ -36,17 +36,19 @@ export const Cascader = (props: ICascaderProps) => { const [items, setItems] = useState(props.options ?? options) const [searchValue, setSearchValue] = useState('') const [selectedValue, setSelectedValue] = useState<(number | string)[]>(props.value ?? []) - const [selectedDisplayValue, setSelectedDisplayValue] = useState(props.value ? (props.value.slice(-1)[0] as any).label : "") + const [selectedDisplayValue, setSelectedDisplayValue] = useState( + props.value ? (props.value.slice(-1)[0] as any).label : '', + ) const [isOpen, setIsOpen] = useState(false) useEffect(() => { setItems(props.options) }, [props.options]) - const onSearch = ({ target: { value: value}}: { target: { value: string}}) => { + const onSearch = ({ target: { value: value } }: { target: { value: string } }) => { if (debouncedLoadData) { if (value.length > 3) { - if (transformOptionsToPaths(items,[]).filter((path) => filter(value, path)).length == 0) { + if (transformOptionsToPaths(items, []).filter(path => filter(value, path)).length == 0) { debouncedLoadData(value) } } @@ -58,9 +60,9 @@ export const Cascader = (props: ICascaderProps) => { return path.some(option => (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1) } - let debouncedLoadData: (value: string) => void; + let debouncedLoadData: (value: string) => void if (props.loadData) { - debouncedLoadData = useCallback(debounce(props.loadData, 500),[]) + debouncedLoadData = useCallback(debounce(props.loadData, 500), []) } const baseProps: IBaseCascaderProps = { @@ -76,13 +78,39 @@ export const Cascader = (props: ICascaderProps) => { }, dropdownRender: menu => (
- onSearch(a)} /> + onSearch(a)} + /> {menu}
), - showSearch: { filter }, + showSearch: { + filter, + render: (inputValue: string, paths: ICascaderOption[]): ReactNode => { + return ( + <> + {paths.map((path: ICascaderOption, index) => { + const lowerLabel = path.label.toLowerCase() + return ( + <> + {highlightMatches(path.label, inputValue.toLowerCase())} + {index < paths.length - 1 ? ' > ' : ''} + + ) + })} + + ) + }, + }, options: items, - onDropdownVisibleChange: value => setIsOpen(value), + onDropdownVisibleChange: value => { + setIsOpen(value) + if (value) setSearchValue('') + }, } let inputClasses = `query-item` @@ -99,24 +127,46 @@ export const Cascader = (props: ICascaderProps) => { status={props.errorMessage ? 'error' : undefined} className={inputClasses} value={selectedDisplayValue ?? selectedValue.slice(-1)} - prefix={props.icon ? : } + prefix={ + props.icon ? ( + + ) : ( + + ) + } /> {props.errorMessage && {props.errorMessage}} ) - function transformOptionsToPaths(options: DefaultOptionType[], prefixPath: DefaultOptionType[]): DefaultOptionType[][] { - let result: DefaultOptionType[][] = []; - options.forEach((option) => { + function highlightMatches(source: string, valueToHighlight: string): ReactNode { + const lowerSource = source.toLowerCase() + return lowerSource.indexOf(valueToHighlight) === -1 ? (<>{ source }) : ( + <> + {source.slice(0, lowerSource.indexOf(valueToHighlight))} + + {source.slice(lowerSource.indexOf(valueToHighlight), lowerSource.indexOf(valueToHighlight) + valueToHighlight.length)} + + {highlightMatches(source.slice(lowerSource.indexOf(valueToHighlight) + valueToHighlight.length), valueToHighlight)} + + ) + } + + function transformOptionsToPaths( + options: DefaultOptionType[], + prefixPath: DefaultOptionType[], + ): DefaultOptionType[][] { + let result: DefaultOptionType[][] = [] + options.forEach(option => { if (option.children && option.children.length > 0) { - const newPrefix = prefixPath.concat([{label: option.label, value: option.value}]); - result = result.concat(transformOptionsToPaths(option.children, newPrefix)); + const newPrefix = prefixPath.concat([{ label: option.label, value: option.value }]) + result = result.concat(transformOptionsToPaths(option.children, newPrefix)) } else { - const path = prefixPath.concat([{label: option.label, value: option.value}]); - result.push(path); + const path = prefixPath.concat([{ label: option.label, value: option.value }]) + result.push(path) } }) - return result; + return result } } diff --git a/src/components/data-entry/QueryItem/query-item.css b/src/components/data-entry/QueryItem/query-item.css index 5002b78b7..629a76f27 100644 --- a/src/components/data-entry/QueryItem/query-item.css +++ b/src/components/data-entry/QueryItem/query-item.css @@ -34,6 +34,7 @@ } .query-item[disabled] { color: var(--mp-query-item-color-disabled) !important; + background-color: var(--mp-query-item-bg-color) !important; border-color: var(--mp-query-item-border-color-disabled) !important; } .query-item.query-item--input-text { @@ -46,7 +47,6 @@ .query-item.query-item--action { color: var(--mp-query-item-action-primary-color) !important; - gap: var(--mp-query-item-gap); } .query-item.query-item--secondary { @@ -59,6 +59,17 @@ border-width: var(--mp-query-item-border-width-active); background-color: var(--mp-query-item-bg-color-active) !important; } +.query-item.query-item--disabled { + color: var(--mp-query-item-action-secondary-color) !important; +} +.query-item.query-item--disabled:hover { + background-color: var(--mp-query-item-bg-color) !important; +} +.query-item.query-item--disabled:active { + border-width: var(--mp-query-item-border-width) !important; + background-color: var(--mp-query-item-bg-color) !important; + box-shadow: none !important; +} .query-item.query-item--selected > input { color: var(--mp-query-item-value-selector-color) !important; font-weight: var(--mp-query-item-value-selector-font-weight) !important; @@ -74,6 +85,9 @@ .query-item .ant-select-selector { padding: 0 !important; } +.query-item__search-highlight { + color: var(--mp-query-item-action-primary-color); +} /* This is temporary until the new icon component is available where sizes can be controlled without CSS */ .query-item__icon {