Initial commit
Build / run (push) Has been cancelled

This commit is contained in:
maher
2026-05-14 13:42:10 +02:00
commit 511fad8199
4595 changed files with 385164 additions and 0 deletions
@@ -0,0 +1,61 @@
import React, {ReactElement, useEffect, useRef, useState} from 'react';
import {SvgIconProps} from '@common/icons/svg-icon';
import {useTrans} from '@common/i18n/use-trans';
import {useListboxContext} from '@common/ui/forms/listbox/listbox-context';
import {ProgressCircle} from '@common/ui/progress/progress-circle';
import {KeyboardArrowDownIcon} from '@common/icons/material/KeyboardArrowDown';
interface Props {
isLoading?: boolean;
icon?: ReactElement<SvgIconProps>;
}
export function ComboboxEndAdornment({isLoading, icon}: Props) {
const timeout = useRef<any>(null);
const {trans} = useTrans();
const [showLoading, setShowLoading] = useState(false);
const {
state: {isOpen, inputValue},
} = useListboxContext();
const lastInputValue = useRef(inputValue);
useEffect(() => {
if (isLoading && !showLoading) {
if (timeout.current === null) {
timeout.current = setTimeout(() => {
setShowLoading(true);
}, 500);
}
// If user is typing, clear the timer and restart since it is a new request
if (inputValue !== lastInputValue.current) {
clearTimeout(timeout.current);
timeout.current = setTimeout(() => {
setShowLoading(true);
}, 500);
}
} else if (!isLoading) {
// If loading is no longer happening, clear any timers and hide the loading circle
setShowLoading(false);
clearTimeout(timeout.current);
timeout.current = null;
}
lastInputValue.current = inputValue;
}, [isLoading, showLoading, inputValue]);
// loading circle should only be displayed if menu is open, if menuTrigger is "manual", or first time load (to stop circle from showing up when user selects an option)
const showLoadingIndicator = showLoading && (isOpen || isLoading);
if (showLoadingIndicator) {
return (
<ProgressCircle
aria-label={trans({message: 'Loading'})}
size="sm"
isIndeterminate
/>
);
}
return icon || <KeyboardArrowDownIcon />;
}
+183
View File
@@ -0,0 +1,183 @@
import React, {ReactElement, Ref} from 'react';
import {BaseFieldPropsWithDom} from '../input-field/base-field-props';
import {Item} from '../listbox/item';
import {useListbox} from '../listbox/use-listbox';
import {IconButton} from '../../buttons/icon-button';
import {TextField} from '../input-field/text-field/text-field';
import {Listbox} from '../listbox/listbox';
import {SvgIconProps} from '@common/icons/svg-icon';
import {useListboxKeyboardNavigation} from '@common/ui/forms/listbox/use-listbox-keyboard-navigation';
import {createEventHandler} from '@common/utils/dom/create-event-handler';
import {ListBoxChildren, ListboxProps} from '../listbox/types';
import {Popover} from '../../overlays/popover';
import {ComboboxEndAdornment} from '@common/ui/forms/combobox/combobox-end-adornment';
export {Item as Option};
export type ComboboxProps<T extends object> = Omit<
BaseFieldPropsWithDom<HTMLInputElement>,
'endAdornment'
> &
ListBoxChildren<T> &
ListboxProps & {
selectionMode?: 'single' | 'none';
isAsync?: boolean;
isLoading?: boolean;
openMenuOnFocus?: boolean;
endAdornmentIcon?: ReactElement<SvgIconProps>;
useOptionLabelAsInputValue?: boolean;
hideEndAdornment?: boolean;
onEndAdornmentClick?: () => void;
prependListbox?: boolean;
listboxClassName?: string;
};
function ComboBox<T extends object>(
props: ComboboxProps<T> & {selectionMode: 'single'},
ref: Ref<HTMLInputElement>,
) {
const {
children,
items,
isAsync,
isLoading,
openMenuOnFocus = true,
endAdornmentIcon,
onItemSelected,
maxItems,
clearInputOnItemSelection,
inputValue: userInputValue,
selectedValue,
onSelectionChange,
allowCustomValue = false,
onInputValueChange,
defaultInputValue,
selectionMode = 'single',
useOptionLabelAsInputValue,
showEmptyMessage,
floatingMaxHeight,
hideEndAdornment = false,
blurReferenceOnItemSelection,
isOpen: propsIsOpen,
onOpenChange: propsOnOpenChange,
prependListbox,
listboxClassName,
onEndAdornmentClick,
autoFocusFirstItem = true,
...textFieldProps
} = props;
const listbox = useListbox(
{
...props,
floatingMaxHeight,
blurReferenceOnItemSelection,
selectionMode,
role: 'listbox',
virtualFocus: true,
clearSelectionOnInputClear: true,
},
ref,
);
const {
reference,
listboxId,
onInputChange,
state: {
isOpen,
setIsOpen,
inputValue,
setInputValue,
selectValues,
selectedValues,
setActiveCollection,
},
collection,
} = listbox;
const textLabel = selectedValues[0]
? collection.get(selectedValues[0])?.textLabel
: undefined;
const {handleListboxSearchFieldKeydown} =
useListboxKeyboardNavigation(listbox);
const handleFocusAndClick = createEventHandler(
(e: React.FocusEvent<HTMLInputElement>) => {
if (openMenuOnFocus && !isOpen) {
setIsOpen(true);
}
e.target.select();
},
);
return (
<Listbox
prepend={prependListbox}
className={listboxClassName}
listbox={listbox}
mobileOverlay={Popover}
isLoading={isLoading}
onPointerDown={e => {
// prevent focus from leaving input when scrolling listbox via mouse
e.preventDefault();
}}
>
<TextField
inputRef={reference}
{...textFieldProps}
endAdornment={
!hideEndAdornment ? (
<IconButton
size="md"
tabIndex={-1}
disabled={textFieldProps.disabled}
className="pointer-events-auto"
onPointerDown={e => {
e.preventDefault();
e.stopPropagation();
if (onEndAdornmentClick) {
onEndAdornmentClick();
} else {
setActiveCollection('all');
setIsOpen(!isOpen);
}
}}
>
<ComboboxEndAdornment
isLoading={isLoading}
icon={endAdornmentIcon}
/>
</IconButton>
) : null
}
aria-expanded={isOpen ? 'true' : 'false'}
aria-haspopup="listbox"
aria-controls={isOpen ? listboxId : undefined}
aria-autocomplete="list"
autoComplete="off"
autoCorrect="off"
spellCheck="false"
onChange={onInputChange}
value={useOptionLabelAsInputValue && textLabel ? textLabel : inputValue}
onBlur={e => {
if (allowCustomValue) {
selectValues(e.target.value);
} else if (!clearInputOnItemSelection) {
const val = selectedValues[0];
setInputValue(selectValues.length && val != null ? `${val}` : '');
}
}}
onFocus={handleFocusAndClick}
onClick={handleFocusAndClick}
onKeyDown={e => handleListboxSearchFieldKeydown(e)}
/>
</Listbox>
);
}
const ComboBoxForwardRef = React.forwardRef(ComboBox) as <T extends object>(
props: ComboboxProps<T> & {ref?: Ref<HTMLInputElement>},
) => ReactElement;
export {ComboBoxForwardRef as ComboBox};
@@ -0,0 +1,32 @@
import {useController} from 'react-hook-form';
import {mergeProps} from '@react-aria/utils';
import React from 'react';
import {ComboBox, ComboboxProps} from './combobox';
type Props<T extends object> = ComboboxProps<T> & {
name: string;
selectionMode?: 'single';
};
export function FormComboBox<T extends object>({children, ...props}: Props<T>) {
const {
field: {onChange, onBlur, value = '', ref},
fieldState: {invalid, error},
} = useController({
name: props.name,
});
const formProps: Partial<ComboboxProps<T>> = {
onSelectionChange: onChange,
onBlur,
selectedValue: value,
defaultInputValue: value,
invalid,
errorMessage: error?.message,
};
return (
<ComboBox ref={ref} {...mergeProps(formProps, props)}>
{children}
</ComboBox>
);
}