Error invalid link (#10288)
Don't have access to push on https://github.com/twentyhq/twenty/pull/9942, so close it and open new PR here <img width="244" alt="Screenshot 2025-02-18 at 11 09 39" src="https://github.com/user-attachments/assets/4bc1b436-147a-4d17-88c8-2aff0fffd06a" /> <img width="246" alt="Screenshot 2025-02-18 at 11 09 51" src="https://github.com/user-attachments/assets/3d7b2972-ab7e-4e3b-a177-658325a3bb70" /> Ok for RecordInlineCell / RecordTableCell and EmailsFieldInput / LinksFieldInput. I think it's too complex for a so small issue (agree with you khuddite) closes https://github.com/twentyhq/twenty/issues/9778 on top of https://github.com/twentyhq/twenty/pull/9942 from @khuddite --------- Co-authored-by: khuddite <khuddite@gmail.com> Co-authored-by: khuddite <62555977+khuddite@users.noreply.github.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -14,6 +14,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
|
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||||
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
||||||
@ -113,7 +114,13 @@ export const CalendarEventDetails = ({
|
|||||||
maxWidth: 300,
|
maxWidth: 300,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordInlineCell readonly />
|
<RecordFieldComponentInstanceContext.Provider
|
||||||
|
value={{
|
||||||
|
instanceId: `${calendarEvent.id}-${fieldName}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecordInlineCell readonly />
|
||||||
|
</RecordFieldComponentInstanceContext.Provider>
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
</StyledPropertyBox>
|
</StyledPropertyBox>
|
||||||
));
|
));
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
RecordUpdateHook,
|
RecordUpdateHook,
|
||||||
RecordUpdateHookParams,
|
RecordUpdateHookParams,
|
||||||
} from '@/object-record/record-field/contexts/FieldContext';
|
} from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||||
@ -61,7 +62,13 @@ export const RecordBoardCardBody = ({
|
|||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordInlineCell />
|
<RecordFieldComponentInstanceContext.Provider
|
||||||
|
value={{
|
||||||
|
instanceId: `board-card-${recordId}-${fieldDefinition.fieldMetadataId}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecordInlineCell />
|
||||||
|
</RecordFieldComponentInstanceContext.Provider>
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
</StopPropagationContainer>
|
</StopPropagationContainer>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import { isFieldRelationToOneObject } from '@/object-record/record-field/types/g
|
|||||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
|
|
||||||
import { ArrayFieldInput } from '@/object-record/record-field/meta-types/input/components/ArrayFieldInput';
|
import { ArrayFieldInput } from '@/object-record/record-field/meta-types/input/components/ArrayFieldInput';
|
||||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
|
||||||
import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress';
|
import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress';
|
||||||
import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
|
import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray';
|
||||||
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
||||||
@ -72,114 +71,108 @@ export const FieldInput = ({
|
|||||||
const { fieldDefinition } = useContext(FieldContext);
|
const { fieldDefinition } = useContext(FieldContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordFieldComponentInstanceContext.Provider
|
<RecordFieldInputScope
|
||||||
value={{
|
recordFieldInputScopeId={getScopeIdFromComponentId(recordFieldInputdId)}
|
||||||
instanceId: recordFieldInputdId,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<RecordFieldInputScope
|
{isFieldRelationToOneObject(fieldDefinition) ? (
|
||||||
recordFieldInputScopeId={getScopeIdFromComponentId(recordFieldInputdId)}
|
<RelationToOneFieldInput onSubmit={onSubmit} onCancel={onCancel} />
|
||||||
>
|
) : isFieldRelationFromManyObjects(fieldDefinition) ? (
|
||||||
{isFieldRelationToOneObject(fieldDefinition) ? (
|
<RelationFromManyFieldInput onSubmit={onSubmit} />
|
||||||
<RelationToOneFieldInput onSubmit={onSubmit} onCancel={onCancel} />
|
) : isFieldPhones(fieldDefinition) ? (
|
||||||
) : isFieldRelationFromManyObjects(fieldDefinition) ? (
|
<PhonesFieldInput
|
||||||
<RelationFromManyFieldInput onSubmit={onSubmit} />
|
onCancel={onCancel}
|
||||||
) : isFieldPhones(fieldDefinition) ? (
|
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
||||||
<PhonesFieldInput
|
/>
|
||||||
onCancel={onCancel}
|
) : isFieldText(fieldDefinition) ? (
|
||||||
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
<TextFieldInput
|
||||||
/>
|
onEnter={onEnter}
|
||||||
) : isFieldText(fieldDefinition) ? (
|
onEscape={onEscape}
|
||||||
<TextFieldInput
|
onClickOutside={onClickOutside}
|
||||||
onEnter={onEnter}
|
onTab={onTab}
|
||||||
onEscape={onEscape}
|
onShiftTab={onShiftTab}
|
||||||
onClickOutside={onClickOutside}
|
/>
|
||||||
onTab={onTab}
|
) : isFieldEmails(fieldDefinition) ? (
|
||||||
onShiftTab={onShiftTab}
|
<EmailsFieldInput
|
||||||
/>
|
onCancel={onCancel}
|
||||||
) : isFieldEmails(fieldDefinition) ? (
|
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
||||||
<EmailsFieldInput
|
/>
|
||||||
onCancel={onCancel}
|
) : isFieldFullName(fieldDefinition) ? (
|
||||||
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
<FullNameFieldInput
|
||||||
/>
|
onEnter={onEnter}
|
||||||
) : isFieldFullName(fieldDefinition) ? (
|
onEscape={onEscape}
|
||||||
<FullNameFieldInput
|
onClickOutside={onClickOutside}
|
||||||
onEnter={onEnter}
|
onTab={onTab}
|
||||||
onEscape={onEscape}
|
onShiftTab={onShiftTab}
|
||||||
onClickOutside={onClickOutside}
|
/>
|
||||||
onTab={onTab}
|
) : isFieldDateTime(fieldDefinition) ? (
|
||||||
onShiftTab={onShiftTab}
|
<DateTimeFieldInput
|
||||||
/>
|
onEnter={onEnter}
|
||||||
) : isFieldDateTime(fieldDefinition) ? (
|
onEscape={onEscape}
|
||||||
<DateTimeFieldInput
|
onClickOutside={onClickOutside}
|
||||||
onEnter={onEnter}
|
onClear={onSubmit}
|
||||||
onEscape={onEscape}
|
onSubmit={onSubmit}
|
||||||
onClickOutside={onClickOutside}
|
/>
|
||||||
onClear={onSubmit}
|
) : isFieldDate(fieldDefinition) ? (
|
||||||
onSubmit={onSubmit}
|
<DateFieldInput
|
||||||
/>
|
onEnter={onEnter}
|
||||||
) : isFieldDate(fieldDefinition) ? (
|
onEscape={onEscape}
|
||||||
<DateFieldInput
|
onClickOutside={onClickOutside}
|
||||||
onEnter={onEnter}
|
onClear={onSubmit}
|
||||||
onEscape={onEscape}
|
onSubmit={onSubmit}
|
||||||
onClickOutside={onClickOutside}
|
/>
|
||||||
onClear={onSubmit}
|
) : isFieldNumber(fieldDefinition) ? (
|
||||||
onSubmit={onSubmit}
|
<NumberFieldInput
|
||||||
/>
|
onEnter={onEnter}
|
||||||
) : isFieldNumber(fieldDefinition) ? (
|
onEscape={onEscape}
|
||||||
<NumberFieldInput
|
onClickOutside={onClickOutside}
|
||||||
onEnter={onEnter}
|
onTab={onTab}
|
||||||
onEscape={onEscape}
|
onShiftTab={onShiftTab}
|
||||||
onClickOutside={onClickOutside}
|
/>
|
||||||
onTab={onTab}
|
) : isFieldLinks(fieldDefinition) ? (
|
||||||
onShiftTab={onShiftTab}
|
<LinksFieldInput
|
||||||
/>
|
onCancel={onCancel}
|
||||||
) : isFieldLinks(fieldDefinition) ? (
|
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
||||||
<LinksFieldInput
|
/>
|
||||||
onCancel={onCancel}
|
) : isFieldCurrency(fieldDefinition) ? (
|
||||||
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
<CurrencyFieldInput
|
||||||
/>
|
onEnter={onEnter}
|
||||||
) : isFieldCurrency(fieldDefinition) ? (
|
onEscape={onEscape}
|
||||||
<CurrencyFieldInput
|
onClickOutside={onClickOutside}
|
||||||
onEnter={onEnter}
|
onTab={onTab}
|
||||||
onEscape={onEscape}
|
onShiftTab={onShiftTab}
|
||||||
onClickOutside={onClickOutside}
|
/>
|
||||||
onTab={onTab}
|
) : isFieldBoolean(fieldDefinition) ? (
|
||||||
onShiftTab={onShiftTab}
|
<BooleanFieldInput onSubmit={onSubmit} readonly={isReadOnly} />
|
||||||
/>
|
) : isFieldRating(fieldDefinition) ? (
|
||||||
) : isFieldBoolean(fieldDefinition) ? (
|
<RatingFieldInput onSubmit={onSubmit} />
|
||||||
<BooleanFieldInput onSubmit={onSubmit} readonly={isReadOnly} />
|
) : isFieldSelect(fieldDefinition) ? (
|
||||||
) : isFieldRating(fieldDefinition) ? (
|
<SelectFieldInput onSubmit={onSubmit} onCancel={onCancel} />
|
||||||
<RatingFieldInput onSubmit={onSubmit} />
|
) : isFieldMultiSelect(fieldDefinition) ? (
|
||||||
) : isFieldSelect(fieldDefinition) ? (
|
<MultiSelectFieldInput onCancel={onCancel} />
|
||||||
<SelectFieldInput onSubmit={onSubmit} onCancel={onCancel} />
|
) : isFieldAddress(fieldDefinition) ? (
|
||||||
) : isFieldMultiSelect(fieldDefinition) ? (
|
<AddressFieldInput
|
||||||
<MultiSelectFieldInput onCancel={onCancel} />
|
onEnter={onEnter}
|
||||||
) : isFieldAddress(fieldDefinition) ? (
|
onEscape={onEscape}
|
||||||
<AddressFieldInput
|
onClickOutside={onClickOutside}
|
||||||
onEnter={onEnter}
|
onTab={onTab}
|
||||||
onEscape={onEscape}
|
onShiftTab={onShiftTab}
|
||||||
onClickOutside={onClickOutside}
|
/>
|
||||||
onTab={onTab}
|
) : isFieldRawJson(fieldDefinition) ? (
|
||||||
onShiftTab={onShiftTab}
|
<RawJsonFieldInput
|
||||||
/>
|
onEnter={onEnter}
|
||||||
) : isFieldRawJson(fieldDefinition) ? (
|
onEscape={onEscape}
|
||||||
<RawJsonFieldInput
|
onClickOutside={onClickOutside}
|
||||||
onEnter={onEnter}
|
onTab={onTab}
|
||||||
onEscape={onEscape}
|
onShiftTab={onShiftTab}
|
||||||
onClickOutside={onClickOutside}
|
/>
|
||||||
onTab={onTab}
|
) : isFieldArray(fieldDefinition) ? (
|
||||||
onShiftTab={onShiftTab}
|
<ArrayFieldInput
|
||||||
/>
|
onCancel={onCancel}
|
||||||
) : isFieldArray(fieldDefinition) ? (
|
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
||||||
<ArrayFieldInput
|
/>
|
||||||
onCancel={onCancel}
|
) : (
|
||||||
onClickOutside={(event) => onClickOutside?.(() => {}, event)}
|
<></>
|
||||||
/>
|
)}
|
||||||
) : (
|
</RecordFieldInputScope>
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</RecordFieldInputScope>
|
|
||||||
</RecordFieldComponentInstanceContext.Provider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { useEmailsField } from '@/object-record/record-field/meta-types/hooks/useEmailsField';
|
import { useEmailsField } from '@/object-record/record-field/meta-types/hooks/useEmailsField';
|
||||||
import { EmailsFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/EmailsFieldMenuItem';
|
import { EmailsFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/EmailsFieldMenuItem';
|
||||||
|
import { recordFieldInputIsFieldInErrorComponentState } from '@/object-record/record-field/states/recordFieldInputIsFieldInErrorComponentState';
|
||||||
import { emailSchema } from '@/object-record/record-field/validation-schemas/emailSchema';
|
import { emailSchema } from '@/object-record/record-field/validation-schemas/emailSchema';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
@ -44,6 +46,14 @@ export const EmailsFieldInput = ({
|
|||||||
|
|
||||||
const isPrimaryEmail = (index: number) => index === 0 && emails?.length > 1;
|
const isPrimaryEmail = (index: number) => index === 0 && emails?.length > 1;
|
||||||
|
|
||||||
|
const setIsFieldInError = useSetRecoilComponentStateV2(
|
||||||
|
recordFieldInputIsFieldInErrorComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleError = (hasError: boolean, values: any[]) => {
|
||||||
|
setIsFieldInError(hasError && values.length === 0);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiItemFieldInput
|
<MultiItemFieldInput
|
||||||
items={emails}
|
items={emails}
|
||||||
@ -70,6 +80,7 @@ export const EmailsFieldInput = ({
|
|||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
onError={handleError}
|
||||||
hotkeyScope={hotkeyScope}
|
hotkeyScope={hotkeyScope}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { useLinksField } from '@/object-record/record-field/meta-types/hooks/useLinksField';
|
import { useLinksField } from '@/object-record/record-field/meta-types/hooks/useLinksField';
|
||||||
import { LinksFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/LinksFieldMenuItem';
|
import { LinksFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/LinksFieldMenuItem';
|
||||||
|
import { recordFieldInputIsFieldInErrorComponentState } from '@/object-record/record-field/states/recordFieldInputIsFieldInErrorComponentState';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { absoluteUrlSchema, isDefined } from 'twenty-shared';
|
import { absoluteUrlSchema, isDefined } from 'twenty-shared';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
@ -47,6 +49,14 @@ export const LinksFieldInput = ({
|
|||||||
|
|
||||||
const isPrimaryLink = (index: number) => index === 0 && links?.length > 1;
|
const isPrimaryLink = (index: number) => index === 0 && links?.length > 1;
|
||||||
|
|
||||||
|
const setIsFieldInError = useSetRecoilComponentStateV2(
|
||||||
|
recordFieldInputIsFieldInErrorComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleError = (hasError: boolean, values: any[]) => {
|
||||||
|
setIsFieldInError(hasError && values.length === 0);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiItemFieldInput
|
<MultiItemFieldInput
|
||||||
items={links}
|
items={links}
|
||||||
@ -59,6 +69,7 @@ export const LinksFieldInput = ({
|
|||||||
isValid: absoluteUrlSchema.safeParse(input).success,
|
isValid: absoluteUrlSchema.safeParse(input).success,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
})}
|
})}
|
||||||
|
onError={handleError}
|
||||||
formatInput={(input) => ({ url: input, label: '' })}
|
formatInput={(input) => ({ url: input, label: '' })}
|
||||||
renderItem={({
|
renderItem={({
|
||||||
value: link,
|
value: link,
|
||||||
|
|||||||
@ -21,6 +21,13 @@ const StyledInput = styled.input<{
|
|||||||
border: 1px solid ${theme.border.color.medium};
|
border: 1px solid ${theme.border.color.medium};
|
||||||
`}
|
`}
|
||||||
|
|
||||||
|
${({ hasError, hasItem, theme }) =>
|
||||||
|
hasError &&
|
||||||
|
hasItem &&
|
||||||
|
css`
|
||||||
|
border: 1px solid ${theme.border.color.danger};
|
||||||
|
`}
|
||||||
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|||||||
@ -36,6 +36,7 @@ type MultiItemFieldInputProps<T> = {
|
|||||||
fieldMetadataType: FieldMetadataType;
|
fieldMetadataType: FieldMetadataType;
|
||||||
renderInput?: MultiItemBaseInputProps['renderInput'];
|
renderInput?: MultiItemBaseInputProps['renderInput'];
|
||||||
onClickOutside?: (event: MouseEvent | TouchEvent) => void;
|
onClickOutside?: (event: MouseEvent | TouchEvent) => void;
|
||||||
|
onError?: (hasError: boolean, values: any[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Todo: the API of this component does not look healthy: we have renderInput, renderItem, formatInput, ...
|
// Todo: the API of this component does not look healthy: we have renderInput, renderItem, formatInput, ...
|
||||||
@ -53,6 +54,7 @@ export const MultiItemFieldInput = <T,>({
|
|||||||
fieldMetadataType,
|
fieldMetadataType,
|
||||||
renderInput,
|
renderInput,
|
||||||
onClickOutside,
|
onClickOutside,
|
||||||
|
onError,
|
||||||
}: MultiItemFieldInputProps<T>) => {
|
}: MultiItemFieldInputProps<T>) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const handleDropdownClose = () => {
|
const handleDropdownClose = () => {
|
||||||
@ -85,6 +87,7 @@ export const MultiItemFieldInput = <T,>({
|
|||||||
setErrorData(
|
setErrorData(
|
||||||
errorData.isValid ? errorData : { isValid: true, errorMessage: '' },
|
errorData.isValid ? errorData : { isValid: true, errorMessage: '' },
|
||||||
);
|
);
|
||||||
|
onError?.(false, items);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddButtonClick = () => {
|
const handleAddButtonClick = () => {
|
||||||
@ -123,6 +126,7 @@ export const MultiItemFieldInput = <T,>({
|
|||||||
if (validateInput !== undefined) {
|
if (validateInput !== undefined) {
|
||||||
const validationData = validateInput(inputValue) ?? { isValid: true };
|
const validationData = validateInput(inputValue) ?? { isValid: true };
|
||||||
if (!validationData.isValid) {
|
if (!validationData.isValid) {
|
||||||
|
onError?.(true, items);
|
||||||
setErrorData(validationData);
|
setErrorData(validationData);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const recordFieldInputIsFieldInErrorComponentState =
|
||||||
|
createComponentStateV2<boolean>({
|
||||||
|
key: 'recordFieldInputIsFieldInErrorComponentState',
|
||||||
|
defaultValue: false,
|
||||||
|
componentInstanceContext: RecordFieldComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -1,9 +1,11 @@
|
|||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { recordFieldInputIsFieldInErrorComponentState } from '@/object-record/record-field/states/recordFieldInputIsFieldInErrorComponentState';
|
||||||
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
|
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
|
||||||
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
|
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
|
||||||
import { RecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
|
import { RecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
|
||||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||||
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {
|
import {
|
||||||
@ -63,6 +65,10 @@ export const RecordInlineCellEditMode = ({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isFieldInError = useRecoilComponentValueV2(
|
||||||
|
recordFieldInputIsFieldInErrorComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const { refs, floatingStyles } = useFloating({
|
const { refs, floatingStyles } = useFloating({
|
||||||
placement: isCentered ? 'bottom' : 'bottom-start',
|
placement: isCentered ? 'bottom' : 'bottom-start',
|
||||||
middleware: [
|
middleware: [
|
||||||
@ -93,6 +99,7 @@ export const RecordInlineCellEditMode = ({
|
|||||||
ref={refs.setFloating}
|
ref={refs.setFloating}
|
||||||
style={floatingStyles}
|
style={floatingStyles}
|
||||||
borderRadius="sm"
|
borderRadius="sm"
|
||||||
|
hasDangerBorder={isFieldInError}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</OverlayContainer>,
|
</OverlayContainer>,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||||
import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader';
|
import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader';
|
||||||
@ -141,7 +142,13 @@ export const FieldsCard = ({
|
|||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordInlineCell loading={recordLoading} />
|
<RecordFieldComponentInstanceContext.Provider
|
||||||
|
value={{
|
||||||
|
instanceId: `${objectRecordId}-${fieldMetadataItem.id}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecordInlineCell loading={recordLoading} />
|
||||||
|
</RecordFieldComponentInstanceContext.Provider>
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import {
|
|||||||
} from '@/object-record/record-field/contexts/FieldContext';
|
} from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||||
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||||
@ -280,7 +281,13 @@ export const RecordDetailRelationRecordsListItem = ({
|
|||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordInlineCell />
|
<RecordFieldComponentInstanceContext.Provider
|
||||||
|
value={{
|
||||||
|
instanceId: `${relationRecord.id}-${fieldMetadataItem.id}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecordInlineCell />
|
||||||
|
</RecordFieldComponentInstanceContext.Provider>
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { recordFieldInputIsFieldInErrorComponentState } from '@/object-record/record-field/states/recordFieldInputIsFieldInErrorComponentState';
|
||||||
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
|
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
|
||||||
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
|
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
|
||||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||||
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {
|
import {
|
||||||
@ -35,6 +37,10 @@ export const RecordTableCellEditMode = ({
|
|||||||
}: RecordTableCellEditModeProps) => {
|
}: RecordTableCellEditModeProps) => {
|
||||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||||
|
|
||||||
|
const isFieldInError = useRecoilComponentValueV2(
|
||||||
|
recordFieldInputIsFieldInErrorComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const instanceId = getRecordFieldInputId(
|
const instanceId = getRecordFieldInputId(
|
||||||
recordId,
|
recordId,
|
||||||
fieldDefinition?.metadata?.fieldName,
|
fieldDefinition?.metadata?.fieldName,
|
||||||
@ -84,6 +90,7 @@ export const RecordTableCellEditMode = ({
|
|||||||
ref={refs.setFloating}
|
ref={refs.setFloating}
|
||||||
style={floatingStyles}
|
style={floatingStyles}
|
||||||
borderRadius="sm"
|
borderRadius="sm"
|
||||||
|
hasDangerBorder={isFieldInError}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</OverlayContainer>
|
</OverlayContainer>
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { ReactNode, useContext } from 'react';
|
|||||||
|
|
||||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||||
@ -87,7 +88,11 @@ export const RecordTableCellFieldContextWrapper = ({
|
|||||||
displayedMaxRows: 1,
|
displayedMaxRows: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
<RecordFieldComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: recordId + columnDefinition.label }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RecordFieldComponentInstanceContext.Provider>
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import styled from '@emotion/styled';
|
|||||||
// eslint-disable-next-line @nx/workspace-styled-components-prefixed-with-styled
|
// eslint-disable-next-line @nx/workspace-styled-components-prefixed-with-styled
|
||||||
export const OverlayContainer = styled.div<{
|
export const OverlayContainer = styled.div<{
|
||||||
borderRadius?: 'sm' | 'md';
|
borderRadius?: 'sm' | 'md';
|
||||||
|
hasDangerBorder?: boolean;
|
||||||
}>`
|
}>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -14,7 +15,9 @@ export const OverlayContainer = styled.div<{
|
|||||||
theme.border.radius[borderRadius ?? 'md']};
|
theme.border.radius[borderRadius ?? 'md']};
|
||||||
|
|
||||||
background: ${({ theme }) => theme.background.transparent.primary};
|
background: ${({ theme }) => theme.background.transparent.primary};
|
||||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
border: 1px solid
|
||||||
|
${({ theme, hasDangerBorder }) =>
|
||||||
|
theme.border.color[hasDangerBorder ? 'danger' : 'medium']};
|
||||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
Reference in New Issue
Block a user