-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
36 changed files
with
206 additions
and
1,630 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 32 additions & 35 deletions
67
vuu-ui/packages/vuu-filters/src/filter-clause/ExpandoCombobox.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,56 @@ | ||
.vuuExpandoCombobox { | ||
--expando-combobox-height: var(--vuuExpandoCombobox-height, var(--salt-size-base)); | ||
--expando-combobox-fontSize: var(--vuuExpandoCombobox-fontSize, 12px); | ||
|
||
--saltInput-outline: none; | ||
--saltInput-fontSize: var(--expando-combobox-fontSize); | ||
--saltInput-height: var(--expando-combobox-height); | ||
--saltInput-minWidth: 4px; | ||
--saltInput-paddingLeft: 0px; | ||
--saltInput-paddingRight: 0px; | ||
|
||
display: inline-flex; | ||
flex-direction: column; | ||
height: var(--expando-combobox-height); | ||
min-width: 4px; | ||
padding: 0; | ||
|
||
} | ||
|
||
.vuuExpandoCombobox .saltInput { | ||
background-color: transparent; | ||
position: absolute; | ||
} | ||
.saltPill { | ||
height: 100%; | ||
padding-block: 0px; | ||
} | ||
|
||
.saltPillInput { | ||
min-height: var(--expando-combobox-height); | ||
min-width: 0px; | ||
} | ||
|
||
|
||
.saltPillInput-inputWrapper { | ||
height: 100%; | ||
min-width: max(var(--salt-spacing-800), 100%); | ||
padding: 0; | ||
} | ||
.saltPillInput-input { | ||
width: 0; | ||
} | ||
|
||
.saltPillInput-endAdornmentContainer { | ||
display: none; | ||
} | ||
.saltPillInput-activationIndicator { | ||
display: none; | ||
} | ||
|
||
.vuuExpandoCombobox .vuuDropdown { | ||
height: 100%; | ||
} | ||
|
||
/** double up the selector just to increase specificity, won't need when we use cascade layers */ | ||
.vuuExpandoCombobox-Input.saltInput { | ||
border: none; | ||
left: 0px; | ||
margin: 0; | ||
min-height: 100%; | ||
padding: 0; | ||
right: 0px; | ||
width: auto; | ||
.vuuExpandoOption { | ||
min-width: calc(var(--salt-size-base) * 2); | ||
padding-left: var(--salt-spacing-200); | ||
padding-right: var(--salt-spacing-200); | ||
} | ||
|
||
.vuuExpandoCombobox .saltInput-input { | ||
border: none; | ||
box-sizing: content-box; | ||
display: block; | ||
flex: 1; | ||
font: inherit; | ||
margin: 0; | ||
min-width: 0; | ||
outline: none; | ||
padding: 0; | ||
} | ||
|
||
.vuuExpandoCombobox:before { | ||
content: attr(data-text); | ||
display: block; | ||
font-size: var(--expando-combobox-fontSize); | ||
height: 0px; | ||
overflow: hidden; | ||
/* visibility: hidden; */ | ||
white-space: pre-wrap; | ||
/* position: absolute; */ | ||
} |
228 changes: 73 additions & 155 deletions
228
vuu-ui/packages/vuu-filters/src/filter-clause/ExpandoCombobox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,196 +1,114 @@ | ||
import { itemToString as defaultToString } from "@finos/vuu-utils"; | ||
import { | ||
ComboBox, | ||
ComboBoxProps, | ||
MultiSelectionHandler, | ||
SelectionStrategy, | ||
SingleSelectionHandler, | ||
} from "@finos/vuu-ui-controls"; | ||
import { useComponentCssInjection } from "@salt-ds/styles"; | ||
import { useWindow } from "@salt-ds/window"; | ||
import cx from "clsx"; | ||
import { ComboBox, ComboBoxProps } from "@salt-ds/core"; | ||
import { | ||
FormEvent, | ||
ChangeEvent, | ||
ForwardedRef, | ||
Ref, | ||
SyntheticEvent, | ||
forwardRef, | ||
ReactElement, | ||
useCallback, | ||
useMemo, | ||
useRef, | ||
useState, | ||
} from "react"; | ||
import { useComponentCssInjection } from "@salt-ds/styles"; | ||
import { useWindow } from "@salt-ds/window"; | ||
|
||
import expandoBoxCss from "./ExpandoCombobox.css"; | ||
import expandoComboboxCss from "./ExpandoCombobox.css"; | ||
|
||
const classBase = "vuuExpandoCombobox"; | ||
|
||
const NO_INPUT_PROPS = {}; | ||
|
||
export interface ExpandoComboboxProps< | ||
Item = string, | ||
S extends SelectionStrategy = "default" | ||
> extends Omit<ComboBoxProps<Item, S>, "itemToString" | "value"> { | ||
itemToString?: (item: unknown) => string; | ||
onInputChange?: (evt: FormEvent<HTMLInputElement>) => void; | ||
value?: string | string[]; | ||
export interface ExpandoComboboxProps<Item = string> | ||
extends ComboBoxProps<Item> { | ||
itemToString?: (item: Item) => string; | ||
} | ||
|
||
const defaultItemToString = (item: unknown) => { | ||
if (typeof item === "string") { | ||
return item; | ||
} else { | ||
return item?.toString() ?? ""; | ||
} | ||
}; | ||
export const ExpandoCombobox = forwardRef(function ExpandoCombobox< | ||
Item = string, | ||
S extends SelectionStrategy = "default" | ||
Item = string | ||
>( | ||
{ | ||
className: classNameProp, | ||
InputProps: InputPropsProp = NO_INPUT_PROPS, | ||
ListProps: ListPropsProp, | ||
onInputChange, | ||
children, | ||
className, | ||
inputProps: inputPropsProp, | ||
itemToString = defaultItemToString, | ||
multiselect, | ||
onChange, | ||
onSelectionChange, | ||
selectionStrategy, | ||
source, | ||
style, | ||
title, | ||
value = "", | ||
value: valueProp, | ||
...props | ||
}: ExpandoComboboxProps<Item, S>, | ||
}: ExpandoComboboxProps<Item>, | ||
forwardedRef: ForwardedRef<HTMLDivElement> | ||
) { | ||
const targetWindow = useWindow(); | ||
useComponentCssInjection({ | ||
testId: "vuu-expando-combobox", | ||
css: expandoBoxCss, | ||
css: expandoComboboxCss, | ||
window: targetWindow, | ||
}); | ||
|
||
const [text, setText] = useState(value); | ||
const { itemToString = defaultToString } = props; | ||
const initialValue = useRef(value); | ||
|
||
const itemsToString = useCallback<<I = Item>(items: I[]) => string>( | ||
(items) => { | ||
const [first, ...rest] = items; | ||
if (rest.length) { | ||
return `${itemToString(first)} + ${rest.length}`; | ||
} else { | ||
return itemToString(first); | ||
} | ||
}, | ||
[itemToString] | ||
const [open, setOpen] = useState(false); | ||
const [value, setValue] = useState( | ||
valueProp === undefined ? "" : valueProp.toString() | ||
); | ||
|
||
const handleInputChange = useCallback( | ||
(evt: FormEvent<HTMLInputElement>) => { | ||
const { value } = evt.target as HTMLInputElement; | ||
setText(value); | ||
onInputChange?.(evt); | ||
}, | ||
[onInputChange] | ||
); | ||
|
||
const handleSetSelectedText = useCallback((text: string) => { | ||
setText(text); | ||
}, []); | ||
|
||
const [InputProps, ListProps] = useMemo< | ||
[ | ||
ComboBoxProps["InputProps"], | ||
Omit<ComboBoxProps["ListProps"], "defaultSelected"> | ||
] | ||
>(() => { | ||
const { inputProps, ...restInputProps } = InputPropsProp; | ||
return [ | ||
{ | ||
...restInputProps, | ||
className: `${classBase}-Input`, | ||
endAdornment: null, | ||
inputProps: { | ||
...inputProps, | ||
autoComplete: "off", | ||
onInput: handleInputChange, | ||
}, | ||
}, | ||
{ | ||
...ListPropsProp, | ||
className: cx("vuuMenuList", ListPropsProp?.className), | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
"data-mode": "light", | ||
displayedItemCount: 10, | ||
itemHeight: 22, | ||
maxWidth: 300, | ||
minWidth: 80, | ||
width: "content-width", | ||
}, | ||
]; | ||
}, [InputPropsProp, handleInputChange, ListPropsProp]); | ||
|
||
const handleSelectionChange = useCallback( | ||
(_, selected) => { | ||
if (Array.isArray(selected)) { | ||
(onSelectionChange as MultiSelectionHandler<Item>)?.( | ||
null, | ||
selected as Item[] | ||
); | ||
} else if (selected) { | ||
setText(itemToString(selected)); | ||
(onSelectionChange as SingleSelectionHandler<Item>)?.( | ||
null, | ||
selected as Item | ||
); | ||
} | ||
}, | ||
[itemToString, onSelectionChange] | ||
); | ||
const handleChange = (evt: ChangeEvent<HTMLInputElement>) => { | ||
const value = evt.target.value; | ||
onChange?.(evt); | ||
setValue(value); | ||
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const getSelected = (): any => { | ||
if (initialValue.current === undefined) { | ||
return undefined; | ||
} else if (Array.isArray(initialValue.current)) { | ||
return source?.filter((item) => | ||
initialValue.current.includes(itemToString(item)) | ||
); | ||
const handleSelectionChange = (evt: SyntheticEvent, newSelected: Item[]) => { | ||
if (multiselect) { | ||
onSelectionChange?.(evt, newSelected); | ||
} else { | ||
return source?.find( | ||
(item) => itemToString(item) === initialValue.current | ||
); | ||
const [selectedValue] = newSelected; | ||
onSelectionChange?.(evt, newSelected); | ||
setValue(itemToString(selectedValue)); | ||
} | ||
}; | ||
|
||
const popupProps = { | ||
minWidth: "fit-content", | ||
}; | ||
const inputProps = useMemo<ComboBoxProps<Item>["inputProps"]>(() => { | ||
return { | ||
autoComplete: "off", | ||
...inputPropsProp, | ||
onFocus: (evt) => { | ||
inputPropsProp?.onFocus?.(evt); | ||
setTimeout(() => { | ||
setOpen(true); | ||
}, 100); | ||
}, | ||
}; | ||
}, [inputPropsProp]); | ||
|
||
// const matchingValues = values.filter((val) => | ||
// val.toLowerCase().startsWith(value.trim().toLowerCase()) | ||
// ); | ||
|
||
return ( | ||
<div | ||
className={cx(classBase, classNameProp)} | ||
data-text={text} | ||
className={cx(classBase, className)} | ||
data-text={value} | ||
ref={forwardedRef} | ||
style={style} | ||
> | ||
<ComboBox<Item, S> | ||
<ComboBox<Item> | ||
{...props} | ||
PopupProps={popupProps} | ||
// allowEnterCommitsText | ||
className="vuuEmbedded" | ||
defaultSelected={getSelected()} | ||
defaultValue={ | ||
Array.isArray(initialValue.current) | ||
? itemsToString<string>(initialValue.current) | ||
: initialValue.current | ||
} | ||
fullWidth | ||
ListProps={ListProps} | ||
InputProps={InputProps} | ||
itemsToString={itemsToString} | ||
inputProps={inputProps} | ||
multiselect={multiselect} | ||
onChange={handleChange} | ||
onOpenChange={setOpen} | ||
onSelectionChange={handleSelectionChange} | ||
onSetSelectedText={handleSetSelectedText} | ||
selectionStrategy={selectionStrategy} | ||
source={source} | ||
/> | ||
open={open} | ||
value={value} | ||
> | ||
{children} | ||
</ComboBox> | ||
</div> | ||
); | ||
}) as <Item, S extends SelectionStrategy = "default">( | ||
props: ExpandoComboboxProps<Item, S> & { | ||
ref?: ForwardedRef<HTMLDivElement>; | ||
} | ||
) => ReactElement<ExpandoComboboxProps<Item, S>>; | ||
}) as <Item = string>( | ||
props: ExpandoComboboxProps<Item> & { ref?: Ref<HTMLDivElement> } | ||
) => JSX.Element; |
Oops, something went wrong.