mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-23 03:51:36 +03:00
Add Select form field (#8815)
Closes https://github.com/twentyhq/twenty/pull/8815 I took inspiration from existing parts of the codebase. Please, see the comments I left below. Remaining questions: - I'm not sure about the best way to handle hotkey scopes in the components easily https://github.com/user-attachments/assets/7a6dd144-d528-4f68-97cd-c9181f3954f9
This commit is contained in:
parent
2c0d3e93d2
commit
9142bdfb92
@ -1,15 +1,18 @@
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
|
||||||
import { FormBooleanFieldInput } from '@/object-record/record-field/form-types/components/FormBooleanFieldInput';
|
import { FormBooleanFieldInput } from '@/object-record/record-field/form-types/components/FormBooleanFieldInput';
|
||||||
import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput';
|
import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput';
|
||||||
|
import { FormSelectFieldInput } from '@/object-record/record-field/form-types/components/FormSelectFieldInput';
|
||||||
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
|
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
|
||||||
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
|
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
|
||||||
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
||||||
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
|
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
|
||||||
|
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||||
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
||||||
import { JsonValue } from 'type-fest';
|
import { JsonValue } from 'type-fest';
|
||||||
|
|
||||||
type FormFieldInputProps = {
|
type FormFieldInputProps = {
|
||||||
field: FieldMetadataItem;
|
field: FieldDefinition<FieldMetadata>;
|
||||||
defaultValue: JsonValue;
|
defaultValue: JsonValue;
|
||||||
onPersist: (value: JsonValue) => void;
|
onPersist: (value: JsonValue) => void;
|
||||||
VariablePicker?: VariablePickerComponent;
|
VariablePicker?: VariablePickerComponent;
|
||||||
@ -23,7 +26,6 @@ export const FormFieldInput = ({
|
|||||||
}: FormFieldInputProps) => {
|
}: FormFieldInputProps) => {
|
||||||
return isFieldNumber(field) ? (
|
return isFieldNumber(field) ? (
|
||||||
<FormNumberFieldInput
|
<FormNumberFieldInput
|
||||||
key={field.id}
|
|
||||||
label={field.label}
|
label={field.label}
|
||||||
defaultValue={defaultValue as string | number | undefined}
|
defaultValue={defaultValue as string | number | undefined}
|
||||||
onPersist={onPersist}
|
onPersist={onPersist}
|
||||||
@ -32,7 +34,6 @@ export const FormFieldInput = ({
|
|||||||
/>
|
/>
|
||||||
) : isFieldBoolean(field) ? (
|
) : isFieldBoolean(field) ? (
|
||||||
<FormBooleanFieldInput
|
<FormBooleanFieldInput
|
||||||
key={field.id}
|
|
||||||
label={field.label}
|
label={field.label}
|
||||||
defaultValue={defaultValue as string | boolean | undefined}
|
defaultValue={defaultValue as string | boolean | undefined}
|
||||||
onPersist={onPersist}
|
onPersist={onPersist}
|
||||||
@ -46,5 +47,13 @@ export const FormFieldInput = ({
|
|||||||
placeholder={field.label}
|
placeholder={field.label}
|
||||||
VariablePicker={VariablePicker}
|
VariablePicker={VariablePicker}
|
||||||
/>
|
/>
|
||||||
|
) : isFieldSelect(field) ? (
|
||||||
|
<FormSelectFieldInput
|
||||||
|
label={field.label}
|
||||||
|
defaultValue={defaultValue as string | undefined}
|
||||||
|
onPersist={onPersist}
|
||||||
|
field={field}
|
||||||
|
VariablePicker={VariablePicker}
|
||||||
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,264 @@
|
|||||||
|
import { StyledFormFieldInputContainer } from '@/object-record/record-field/form-types/components/StyledFormFieldInputContainer';
|
||||||
|
import { StyledFormFieldInputInputContainer } from '@/object-record/record-field/form-types/components/StyledFormFieldInputInputContainer';
|
||||||
|
import { StyledFormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/StyledFormFieldInputRowContainer';
|
||||||
|
import { VariableChip } from '@/object-record/record-field/form-types/components/VariableChip';
|
||||||
|
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
|
||||||
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
|
import { FieldSelectMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
|
import { SINGLE_RECORD_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleRecordSelectBaseList';
|
||||||
|
import { SelectOption } from '@/spreadsheet-import/types';
|
||||||
|
import { SelectDisplay } from '@/ui/field/display/components/SelectDisplay';
|
||||||
|
import { SelectInput } from '@/ui/field/input/components/SelectInput';
|
||||||
|
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||||
|
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||||
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useId, useState } from 'react';
|
||||||
|
import { Key } from 'ts-key-enum';
|
||||||
|
import { isDefined, VisibilityHidden } from 'twenty-ui';
|
||||||
|
|
||||||
|
type FormSelectFieldInputProps = {
|
||||||
|
field: FieldDefinition<FieldSelectMetadata>;
|
||||||
|
label?: string;
|
||||||
|
defaultValue: string | undefined;
|
||||||
|
onPersist: (value: number | null | string) => void;
|
||||||
|
VariablePicker?: VariablePickerComponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledDisplayModeContainer = styled.button`
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
padding-inline: ${({ theme }) => theme.spacing(2)};
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&[data-open='true'] {
|
||||||
|
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FormSelectFieldInput = ({
|
||||||
|
label,
|
||||||
|
field,
|
||||||
|
defaultValue,
|
||||||
|
onPersist,
|
||||||
|
VariablePicker,
|
||||||
|
}: FormSelectFieldInputProps) => {
|
||||||
|
const inputId = useId();
|
||||||
|
|
||||||
|
const hotkeyScope = InlineCellHotkeyScope.InlineCell;
|
||||||
|
|
||||||
|
const {
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
|
goBackToPreviousHotkeyScope,
|
||||||
|
} = usePreviousHotkeyScope();
|
||||||
|
|
||||||
|
const [draftValue, setDraftValue] = useState<
|
||||||
|
| {
|
||||||
|
type: 'static';
|
||||||
|
value: string;
|
||||||
|
editingMode: 'view' | 'edit';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'variable';
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
>(
|
||||||
|
isStandaloneVariableString(defaultValue)
|
||||||
|
? {
|
||||||
|
type: 'variable',
|
||||||
|
value: defaultValue,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'static',
|
||||||
|
value: isDefined(defaultValue) ? String(defaultValue) : '',
|
||||||
|
editingMode: 'view',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSubmit = (option: string) => {
|
||||||
|
setDraftValue({
|
||||||
|
type: 'static',
|
||||||
|
value: option,
|
||||||
|
editingMode: 'view',
|
||||||
|
});
|
||||||
|
|
||||||
|
goBackToPreviousHotkeyScope();
|
||||||
|
|
||||||
|
onPersist(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
if (draftValue.type !== 'static') {
|
||||||
|
throw new Error('Can only be called when editing a static value');
|
||||||
|
}
|
||||||
|
|
||||||
|
setDraftValue({
|
||||||
|
...draftValue,
|
||||||
|
editingMode: 'view',
|
||||||
|
});
|
||||||
|
|
||||||
|
goBackToPreviousHotkeyScope();
|
||||||
|
};
|
||||||
|
|
||||||
|
const [selectWrapperRef, setSelectWrapperRef] =
|
||||||
|
useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>([]);
|
||||||
|
|
||||||
|
const { resetSelectedItem } = useSelectableList(
|
||||||
|
SINGLE_RECORD_SELECT_BASE_LIST,
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearField = () => {
|
||||||
|
setDraftValue({
|
||||||
|
type: 'static',
|
||||||
|
editingMode: 'view',
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
onPersist(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedOption = field.metadata.options.find(
|
||||||
|
(option) => option.value === draftValue.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClearField = () => {
|
||||||
|
clearField();
|
||||||
|
|
||||||
|
goBackToPreviousHotkeyScope();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (option: SelectOption) => {
|
||||||
|
onSubmit(option.value);
|
||||||
|
|
||||||
|
resetSelectedItem();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnlinkVariable = () => {
|
||||||
|
setDraftValue({
|
||||||
|
type: 'static',
|
||||||
|
value: '',
|
||||||
|
editingMode: 'view',
|
||||||
|
});
|
||||||
|
|
||||||
|
onPersist(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVariableTagInsert = (variableName: string) => {
|
||||||
|
setDraftValue({
|
||||||
|
type: 'variable',
|
||||||
|
value: variableName,
|
||||||
|
});
|
||||||
|
|
||||||
|
onPersist(variableName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDisplayModeClick = () => {
|
||||||
|
if (draftValue.type !== 'static') {
|
||||||
|
throw new Error(
|
||||||
|
'This function can only be called when editing a static value.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDraftValue({
|
||||||
|
...draftValue,
|
||||||
|
editingMode: 'edit',
|
||||||
|
});
|
||||||
|
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope(hotkeyScope);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectEnter = (itemId: string) => {
|
||||||
|
const option = filteredOptions.find((option) => option.value === itemId);
|
||||||
|
if (isDefined(option)) {
|
||||||
|
onSubmit(option.value);
|
||||||
|
resetSelectedItem();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
Key.Escape,
|
||||||
|
() => {
|
||||||
|
onCancel();
|
||||||
|
resetSelectedItem();
|
||||||
|
},
|
||||||
|
hotkeyScope,
|
||||||
|
[onCancel, resetSelectedItem],
|
||||||
|
);
|
||||||
|
|
||||||
|
const optionIds = [
|
||||||
|
`No ${field.label}`,
|
||||||
|
...filteredOptions.map((option) => option.value),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledFormFieldInputContainer>
|
||||||
|
{label ? <InputLabel>{label}</InputLabel> : null}
|
||||||
|
|
||||||
|
<StyledFormFieldInputRowContainer>
|
||||||
|
<StyledFormFieldInputInputContainer
|
||||||
|
ref={setSelectWrapperRef}
|
||||||
|
hasRightElement={isDefined(VariablePicker)}
|
||||||
|
>
|
||||||
|
{draftValue.type === 'static' ? (
|
||||||
|
<>
|
||||||
|
<StyledDisplayModeContainer
|
||||||
|
data-open={draftValue.editingMode === 'edit'}
|
||||||
|
onClick={handleDisplayModeClick}
|
||||||
|
>
|
||||||
|
<VisibilityHidden>Edit</VisibilityHidden>
|
||||||
|
|
||||||
|
{isDefined(selectedOption) ? (
|
||||||
|
<SelectDisplay
|
||||||
|
color={selectedOption.color}
|
||||||
|
label={selectedOption.label}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</StyledDisplayModeContainer>
|
||||||
|
|
||||||
|
{draftValue.editingMode === 'edit' ? (
|
||||||
|
<SelectInput
|
||||||
|
selectableListId={SINGLE_RECORD_SELECT_BASE_LIST}
|
||||||
|
selectableItemIdArray={optionIds}
|
||||||
|
hotkeyScope={hotkeyScope}
|
||||||
|
onEnter={handleSelectEnter}
|
||||||
|
selectWrapperRef={selectWrapperRef}
|
||||||
|
onOptionSelected={handleSubmit}
|
||||||
|
options={field.metadata.options}
|
||||||
|
onCancel={onCancel}
|
||||||
|
defaultOption={selectedOption}
|
||||||
|
onFilterChange={setFilteredOptions}
|
||||||
|
onClear={
|
||||||
|
field.metadata.isNullable ? handleClearField : undefined
|
||||||
|
}
|
||||||
|
clearLabel={field.label}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<VariableChip
|
||||||
|
rawVariableName={draftValue.value}
|
||||||
|
onRemove={handleUnlinkVariable}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StyledFormFieldInputInputContainer>
|
||||||
|
|
||||||
|
{VariablePicker ? (
|
||||||
|
<VariablePicker
|
||||||
|
inputId={inputId}
|
||||||
|
onVariableSelect={handleVariableTagInsert}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</StyledFormFieldInputRowContainer>
|
||||||
|
</StyledFormFieldInputContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -1,6 +1,5 @@
|
|||||||
import { Tag } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { useSelectFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useSelectFieldDisplay';
|
import { useSelectFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useSelectFieldDisplay';
|
||||||
|
import { SelectDisplay } from '@/ui/field/display/components/SelectDisplay';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const SelectFieldDisplay = () => {
|
export const SelectFieldDisplay = () => {
|
||||||
@ -15,10 +14,6 @@ export const SelectFieldDisplay = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tag
|
<SelectDisplay color={selectedOption.color} label={selectedOption.label} />
|
||||||
preventShrink
|
|
||||||
color={selectedOption.color}
|
|
||||||
text={selectedOption.label}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,8 +3,7 @@ import { useSelectField } from '@/object-record/record-field/meta-types/hooks/us
|
|||||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||||
import { SINGLE_RECORD_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleRecordSelectBaseList';
|
import { SINGLE_RECORD_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleRecordSelectBaseList';
|
||||||
import { SelectOption } from '@/spreadsheet-import/types';
|
import { SelectOption } from '@/spreadsheet-import/types';
|
||||||
import { SelectInput } from '@/ui/input/components/SelectInput';
|
import { SelectInput } from '@/ui/field/input/components/SelectInput';
|
||||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
|
||||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@ -64,7 +63,7 @@ export const SelectFieldInput = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={setSelectWrapperRef}>
|
<div ref={setSelectWrapperRef}>
|
||||||
<SelectableList
|
<SelectInput
|
||||||
selectableListId={SINGLE_RECORD_SELECT_BASE_LIST}
|
selectableListId={SINGLE_RECORD_SELECT_BASE_LIST}
|
||||||
selectableItemIdArray={optionIds}
|
selectableItemIdArray={optionIds}
|
||||||
hotkeyScope={hotkeyScope}
|
hotkeyScope={hotkeyScope}
|
||||||
@ -77,21 +76,17 @@ export const SelectFieldInput = ({
|
|||||||
resetSelectedItem();
|
resetSelectedItem();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
selectWrapperRef={selectWrapperRef}
|
||||||
<SelectInput
|
onOptionSelected={handleSubmit}
|
||||||
parentRef={selectWrapperRef}
|
options={fieldDefinition.metadata.options}
|
||||||
onOptionSelected={handleSubmit}
|
onCancel={onCancel}
|
||||||
options={fieldDefinition.metadata.options}
|
defaultOption={selectedOption}
|
||||||
onCancel={onCancel}
|
onFilterChange={setFilteredOptions}
|
||||||
defaultOption={selectedOption}
|
onClear={
|
||||||
onFilterChange={setFilteredOptions}
|
fieldDefinition.metadata.isNullable ? handleClearField : undefined
|
||||||
onClear={
|
}
|
||||||
fieldDefinition.metadata.isNullable ? handleClearField : undefined
|
clearLabel={fieldDefinition.label}
|
||||||
}
|
/>
|
||||||
clearLabel={fieldDefinition.label}
|
|
||||||
hotkeyScope={hotkeyScope}
|
|
||||||
/>
|
|
||||||
</SelectableList>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { Tag, ThemeColor } from 'twenty-ui';
|
||||||
|
|
||||||
|
type SelectDisplayProps = {
|
||||||
|
color: ThemeColor;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SelectDisplay = ({ color, label }: SelectDisplayProps) => {
|
||||||
|
return <Tag preventShrink color={color} text={label} />;
|
||||||
|
};
|
@ -0,0 +1,55 @@
|
|||||||
|
import { SelectOption } from '@/spreadsheet-import/types';
|
||||||
|
import { SelectInput as SelectBaseInput } from '@/ui/input/components/SelectInput';
|
||||||
|
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||||
|
import { ReferenceType } from '@floating-ui/react';
|
||||||
|
|
||||||
|
type SelectInputProps = {
|
||||||
|
selectableListId: string;
|
||||||
|
selectableItemIdArray: string[];
|
||||||
|
hotkeyScope: string;
|
||||||
|
onEnter: (itemId: string) => void;
|
||||||
|
selectWrapperRef?: ReferenceType | null | undefined;
|
||||||
|
onOptionSelected: (selectedOption: SelectOption) => void;
|
||||||
|
options: SelectOption[];
|
||||||
|
onCancel?: () => void;
|
||||||
|
defaultOption?: SelectOption | undefined;
|
||||||
|
onFilterChange?: ((filteredOptions: SelectOption[]) => void) | undefined;
|
||||||
|
onClear?: (() => void) | undefined;
|
||||||
|
clearLabel?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SelectInput = ({
|
||||||
|
selectableListId,
|
||||||
|
selectableItemIdArray,
|
||||||
|
hotkeyScope,
|
||||||
|
onEnter,
|
||||||
|
selectWrapperRef,
|
||||||
|
onOptionSelected,
|
||||||
|
options,
|
||||||
|
onCancel,
|
||||||
|
defaultOption,
|
||||||
|
onFilterChange,
|
||||||
|
onClear,
|
||||||
|
clearLabel,
|
||||||
|
}: SelectInputProps) => {
|
||||||
|
return (
|
||||||
|
<SelectableList
|
||||||
|
selectableListId={selectableListId}
|
||||||
|
selectableItemIdArray={selectableItemIdArray}
|
||||||
|
hotkeyScope={hotkeyScope}
|
||||||
|
onEnter={onEnter}
|
||||||
|
>
|
||||||
|
<SelectBaseInput
|
||||||
|
parentRef={selectWrapperRef}
|
||||||
|
onOptionSelected={onOptionSelected}
|
||||||
|
options={options}
|
||||||
|
onCancel={onCancel}
|
||||||
|
defaultOption={defaultOption}
|
||||||
|
onFilterChange={onFilterChange}
|
||||||
|
onClear={onClear}
|
||||||
|
clearLabel={clearLabel}
|
||||||
|
hotkeyScope={hotkeyScope}
|
||||||
|
/>
|
||||||
|
</SelectableList>
|
||||||
|
);
|
||||||
|
};
|
@ -18,10 +18,8 @@ export const EditableFilterChip = ({
|
|||||||
<SortOrFilterChip
|
<SortOrFilterChip
|
||||||
key={viewFilter.id}
|
key={viewFilter.id}
|
||||||
testId={viewFilter.id}
|
testId={viewFilter.id}
|
||||||
labelKey={viewFilter.definition.label}
|
labelKey={`${viewFilter.definition.label}${getOperandLabelShort(viewFilter.operand)}`}
|
||||||
labelValue={`${getOperandLabelShort(viewFilter.operand)} ${
|
labelValue={viewFilter.displayValue}
|
||||||
viewFilter.displayValue
|
|
||||||
}`}
|
|
||||||
Icon={getIcon(viewFilter.definition.iconName)}
|
Icon={getIcon(viewFilter.definition.iconName)}
|
||||||
onRemove={onRemove}
|
onRemove={onRemove}
|
||||||
/>
|
/>
|
||||||
|
@ -33,35 +33,44 @@ const StyledChip = styled.div<{ variant: SortOrFitlerChipVariant }>`
|
|||||||
return theme.color.blue;
|
return theme.color.blue;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
height: 26px;
|
||||||
|
box-sizing: border-box;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
padding: ${({ theme }) => theme.spacing(0.5) + ' ' + theme.spacing(2)};
|
padding: ${({ theme }) => theme.spacing(0.5)};
|
||||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||||
|
column-gap: ${({ theme }) => theme.spacing(1)};
|
||||||
user-select: none;
|
user-select: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
max-height: ${({ theme }) => theme.spacing(4.5)};
|
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledIcon = styled.div`
|
const StyledIcon = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledDelete = styled.div<{ variant: SortOrFitlerChipVariant }>`
|
const StyledDelete = styled.button<{ variant: SortOrFitlerChipVariant }>`
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: ${({ theme }) => theme.spacing(0.5)};
|
|
||||||
display: flex;
|
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
|
||||||
margin-top: 1px;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: ${({ theme, variant }) => {
|
background-color: ${({ theme, variant }) => {
|
||||||
switch (variant) {
|
switch (variant) {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
|
||||||
import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput';
|
import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput';
|
||||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||||
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
|
||||||
@ -55,6 +57,33 @@ export const WorkflowEditActionFormRecordCreate = ({
|
|||||||
});
|
});
|
||||||
const isFormDisabled = actionOptions.readonly;
|
const isFormDisabled = actionOptions.readonly;
|
||||||
|
|
||||||
|
const objectNameSingular = formData.objectName;
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const inlineFieldMetadataItems = objectMetadataItem.fields
|
||||||
|
.filter(
|
||||||
|
(fieldMetadataItem) =>
|
||||||
|
fieldMetadataItem.type !== FieldMetadataType.Relation &&
|
||||||
|
!fieldMetadataItem.isSystem &&
|
||||||
|
fieldMetadataItem.isActive,
|
||||||
|
)
|
||||||
|
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
||||||
|
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
|
||||||
|
);
|
||||||
|
|
||||||
|
const inlineFieldDefinitions = inlineFieldMetadataItems.map(
|
||||||
|
(fieldMetadataItem) =>
|
||||||
|
formatFieldMetadataItemAsFieldDefinition({
|
||||||
|
field: fieldMetadataItem,
|
||||||
|
objectMetadataItem,
|
||||||
|
showLabel: true,
|
||||||
|
labelWidth: 90,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const handleFieldChange = (
|
const handleFieldChange = (
|
||||||
fieldName: keyof CreateRecordFormData,
|
fieldName: keyof CreateRecordFormData,
|
||||||
updatedValue: JsonValue,
|
updatedValue: JsonValue,
|
||||||
@ -76,23 +105,6 @@ export const WorkflowEditActionFormRecordCreate = ({
|
|||||||
});
|
});
|
||||||
}, [action.settings.input]);
|
}, [action.settings.input]);
|
||||||
|
|
||||||
const selectedObjectMetadataItemNameSingular = formData.objectName;
|
|
||||||
|
|
||||||
const selectedObjectMetadataItem = activeObjectMetadataItems.find(
|
|
||||||
(item) => item.nameSingular === selectedObjectMetadataItemNameSingular,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isDefined(selectedObjectMetadataItem)) {
|
|
||||||
throw new Error('Should have found the metadata item');
|
|
||||||
}
|
|
||||||
|
|
||||||
const editableFields = selectedObjectMetadataItem.fields.filter(
|
|
||||||
(field) =>
|
|
||||||
field.type !== FieldMetadataType.Relation &&
|
|
||||||
!field.isSystem &&
|
|
||||||
field.isActive,
|
|
||||||
);
|
|
||||||
|
|
||||||
const saveAction = useDebouncedCallback(
|
const saveAction = useDebouncedCallback(
|
||||||
async (formData: CreateRecordFormData) => {
|
async (formData: CreateRecordFormData) => {
|
||||||
if (actionOptions.readonly === true) {
|
if (actionOptions.readonly === true) {
|
||||||
@ -162,16 +174,16 @@ export const WorkflowEditActionFormRecordCreate = ({
|
|||||||
|
|
||||||
<HorizontalSeparator noMargin />
|
<HorizontalSeparator noMargin />
|
||||||
|
|
||||||
{editableFields.map((field) => {
|
{inlineFieldDefinitions.map((field) => {
|
||||||
const currentValue = formData[field.name] as JsonValue;
|
const currentValue = formData[field.metadata.fieldName] as JsonValue;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormFieldInput
|
<FormFieldInput
|
||||||
key={field.id}
|
key={field.metadata.fieldName}
|
||||||
defaultValue={currentValue}
|
defaultValue={currentValue}
|
||||||
field={field}
|
field={field}
|
||||||
onPersist={(value) => {
|
onPersist={(value) => {
|
||||||
handleFieldChange(field.name, value);
|
handleFieldChange(field.metadata.fieldName, value);
|
||||||
}}
|
}}
|
||||||
VariablePicker={WorkflowVariablePicker}
|
VariablePicker={WorkflowVariablePicker}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user