diff --git a/demo/src/InputsTab.jsx b/demo/src/InputsTab.jsx
index 7553eaa6..e52599aa 100644
--- a/demo/src/InputsTab.jsx
+++ b/demo/src/InputsTab.jsx
@@ -24,6 +24,8 @@ import {
ExpandingTextField,
CustomFormProvider,
SelectClearable,
+ DirectoryItemsInput,
+ ElementType,
} from '../../src';
const AUTOCOMPLETE_INPUT = 'autocomplete';
@@ -36,6 +38,7 @@ const INTEGER_INPUT = 'integer';
const FLOAT_INPUT = 'float';
const CHECKBOX_INPUT = 'checkbox';
const SWITCH_INPUT = 'switch';
+const DIRECTORY_ITEMS_INPUT = 'directoryItems';
const emptyFormData = {
[AUTOCOMPLETE_INPUT]: null,
@@ -48,6 +51,7 @@ const emptyFormData = {
[FLOAT_INPUT]: null,
[CHECKBOX_INPUT]: false, // or null, but should then be nullable in schema
[SWITCH_INPUT]: false, // or null, but should then be nullable in schema
+ [DIRECTORY_ITEMS_INPUT]: [],
};
const formSchema = yup.object().shape({
@@ -61,6 +65,7 @@ const formSchema = yup.object().shape({
[FLOAT_INPUT]: yup.number().nullable(),
[CHECKBOX_INPUT]: yup.boolean(),
[SWITCH_INPUT]: yup.boolean(),
+ [DIRECTORY_ITEMS_INPUT]: yup.array().of(yup.object()).default([]),
});
const options = [
@@ -166,6 +171,17 @@ function InputsTab() {
+
+
+
({
- borderColor: theme.palette.error.main,
- }),
addDirectoryElements: {
marginTop: '-5px',
},
+ inputLabel: {
+ left: '30px',
+ '&.MuiInputLabel-shrink': {
+ transform: 'translate(-16px, -9px) scale(0.75)',
+ },
+ },
} as const satisfies MuiStyles;
export interface DirectoryItemsInputProps {
@@ -60,6 +69,7 @@ export interface DirectoryItemsInputProps;
chipProps?: Partial;
fullHeight?: boolean;
+ fullWidth?: boolean;
}
export function DirectoryItemsInput({
@@ -77,7 +87,8 @@ export function DirectoryItemsInput>) {
const { snackError } = useSnackMessage();
const intl = useIntl();
@@ -136,8 +147,9 @@ export function DirectoryItemsInput {
+ const handleDeleteChip = useCallback(
+ (event: React.MouseEvent, index: number) => {
+ event.stopPropagation();
const elemToRemove = getValues(name)[index];
remove(index);
const newElems = getValues(name); // must call getValues again to get the newly updated array
@@ -147,7 +159,7 @@ export function DirectoryItemsInput {
const chips = getValues(name);
const chip = chips.at(index)?.id;
@@ -165,10 +177,32 @@ export function DirectoryItemsInput {
+ event.stopPropagation();
+ openItemsSelector(index);
+ },
+ [openItemsSelector]
+ );
+
const shouldReplaceElement = useMemo(() => {
return allowMultiSelect === false && elements?.length === 1;
}, [allowMultiSelect, elements]);
+ const handleClickInput = useCallback(() => {
+ if (disable) {
+ return;
+ }
+ if (shouldReplaceElement) {
+ openItemsSelector(0);
+ } else {
+ setDirectoryItemSelectorOpen(true);
+ if (allowMultiSelect) {
+ setMultiSelect(true);
+ }
+ }
+ }, [shouldReplaceElement, openItemsSelector, allowMultiSelect, disable]);
+
const hasElementsWithoutName = useMemo(() => {
const elementsToCheck = (watchedElements ?? elements) as FieldValues[] | undefined;
@@ -191,78 +225,103 @@ export function DirectoryItemsInput
+ );
+
+ const hasElements = elements && elements.length > 0;
+
+ const fullHeightSx = fullHeight ? { height: '100%' } : undefined;
+
+ // 6ch for folder + approximate width of the label text
+ const selectWidth = label ? `${6 + label.length * 0.8}ch` : 'auto';
+
+ // To keep folder icon visible and in the same flexbox as chips, we render it in renderValue (not startAdornment).
+ // This also requires manually controlling label shrink/notch and setting displayEmpty to true.
return (
- <>
+
-
-
- {
- if (shouldReplaceElement) {
- handleChipClick(0);
- } else {
- setDirectoryItemSelectorOpen(true);
- if (allowMultiSelect) {
- setMultiSelect(true);
- }
- }
- }}
- >
-
-
-
-
- {elements?.map((item, index) => {
- const elementName =
- watchedElements?.[index]?.[NAME] ??
- getValues(`${name}.${index}.${NAME}`) ??
- (item as FieldValues)?.[NAME];
+ {label && (
+
+ {fullLabel}
+
+ )}
+
+ }
+ renderValue={(directoryElements: TreeViewFinderNodeProps[]) => (
+
+
+
+
+
+
+
+
+ {directoryElements?.map((item, index) => {
+ const elementName =
+ watchedElements?.[index]?.[NAME] ??
+ getValues(`${name}.${index}.${NAME}`) ??
+ (item as FieldValues)?.[NAME];
- const equipmentTypeShortLabel = getEquipmentTypeShortLabel(item?.specificMetadata?.equipmentType);
+ const equipmentTypeShortLabel = getEquipmentTypeShortLabel(
+ item?.specificMetadata?.equipmentType
+ );
- const { sx: chipSx, ...otherChipProps } = chipProps ?? {};
+ const { sx: chipSx, ...otherChipProps } = chipProps ?? {};
- return (
- removeElements(index)}
- onClick={() => handleChipClick(index)}
- label={elementName || intl.formatMessage({ id: 'elementNotFound' })}
- {...(equipmentTypeShortLabel && {
- helperText: intl.formatMessage({
- id: equipmentTypeShortLabel,
- }),
+ return (
+ handleDeleteChip(e, index)}
+ onClick={(e) => handleClickChip(e, index)}
+ label={elementName || intl.formatMessage({ id: 'elementNotFound' })}
+ {...(equipmentTypeShortLabel && {
+ helperText: intl.formatMessage({
+ id: equipmentTypeShortLabel,
+ }),
+ })}
+ sx={mergeSx(
+ !elementName
+ ? (theme) => ({
+ backgroundColor: theme.palette.error.light,
+ borderColor: theme.palette.error.main,
+ color: theme.palette.error.contrastText,
+ })
+ : undefined,
+ chipSx
+ )}
+ {...(otherChipProps as CP)}
+ />
+ );
})}
- sx={mergeSx(
- !elementName
- ? (theme) => ({
- backgroundColor: theme.palette.error.light,
- borderColor: theme.palette.error.main,
- color: theme.palette.error.contrastText,
- })
- : undefined,
- chipSx
- )}
- {...(otherChipProps as CP)}
- />
- );
- })}
- {elements?.length === 0 && label && (
-
- )}
+
+ )}
+ />
{!hideErrorMessage && }
- >
+
);
}
diff --git a/src/components/inputs/reactHookForm/OverflowableChip.tsx b/src/components/inputs/reactHookForm/OverflowableChip.tsx
index 6d1c59d1..ba83e9da 100644
--- a/src/components/inputs/reactHookForm/OverflowableChip.tsx
+++ b/src/components/inputs/reactHookForm/OverflowableChip.tsx
@@ -5,27 +5,31 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.ds
*/
-import { Box, Chip, type ChipProps } from '@mui/material';
+import { Box, BoxProps, Chip, type ChipProps } from '@mui/material';
import { OverflowableText } from '../../overflowableText';
-export interface OverflowableChipProps extends ChipProps {}
+export interface OverflowableChipProps extends ChipProps {
+ boxSx?: BoxProps['sx'];
+}
-export function OverflowableChip({ label, ...otherProps }: Readonly) {
+export function OverflowableChip({ label, boxSx, ...otherProps }: Readonly) {
return (
-
-
-
- }
- {...otherProps}
- />
+ e.stopPropagation()}>
+
+
+
+ }
+ {...otherProps}
+ />
+
);
}
diff --git a/src/components/inputs/reactHookForm/OverflowableChipWithHelperText.tsx b/src/components/inputs/reactHookForm/OverflowableChipWithHelperText.tsx
index 30901ecd..9c4e171c 100644
--- a/src/components/inputs/reactHookForm/OverflowableChipWithHelperText.tsx
+++ b/src/components/inputs/reactHookForm/OverflowableChipWithHelperText.tsx
@@ -7,6 +7,7 @@
import { Box, FormHelperText } from '@mui/material';
import { OverflowableChip, OverflowableChipProps } from './OverflowableChip';
+import { mergeSx } from '../../../utils';
export interface OverflowableChipWithHelperTextProps extends OverflowableChipProps {
helperText?: string;
@@ -14,12 +15,21 @@ export interface OverflowableChipWithHelperTextProps extends OverflowableChipPro
export function OverflowableChipWithHelperText({
helperText,
+ boxSx,
...otherProps
}: Readonly) {
return (
-
-
- {helperText && {helperText}}
+ e.stopPropagation()}
+ >
+
+ {helperText && (
+ {helperText}
+ )}
);
}
diff --git a/src/components/overflowableText/OverflowableText.tsx b/src/components/overflowableText/OverflowableText.tsx
index 58445375..345a005e 100644
--- a/src/components/overflowableText/OverflowableText.tsx
+++ b/src/components/overflowableText/OverflowableText.tsx
@@ -86,7 +86,15 @@ export const OverflowableText = styled(
const tooltipStyleProps = {
...(tooltipStyle && { classes: { tooltip: tooltipStyle } }),
...(finalTooltipSx && {
- slotProps: { tooltip: { sx: finalTooltipSx } },
+ slotProps: {
+ tooltip: {
+ onClick: (e: React.MouseEvent) => e.stopPropagation(),
+ sx: finalTooltipSx,
+ },
+ popper: {
+ onClick: (e: React.MouseEvent) => e.stopPropagation(),
+ },
+ },
}),
};
diff --git a/src/translations/en/inputsEn.ts b/src/translations/en/inputsEn.ts
index 3cd06778..d8352ed1 100644
--- a/src/translations/en/inputsEn.ts
+++ b/src/translations/en/inputsEn.ts
@@ -21,5 +21,5 @@ export const inputsEn = {
'inputs/boolean': 'Boolean',
'inputs/checkbox': 'Checkbox',
'inputs/switch': 'Switch',
- Optional: 'Optional',
+ 'inputs/directory-items': 'Directory items',
};
diff --git a/src/translations/fr/inputsFr.ts b/src/translations/fr/inputsFr.ts
index 742c7b8f..0154233a 100644
--- a/src/translations/fr/inputsFr.ts
+++ b/src/translations/fr/inputsFr.ts
@@ -21,5 +21,5 @@ export const inputsFr = {
'inputs/boolean': 'Boolean',
'inputs/checkbox': 'Checkbox',
'inputs/switch': 'Switch',
- Optional: 'Optional',
+ 'inputs/directory-items': 'Éléments du répertoire',
};