Add address composite form field (#9022)
- Create FormCountrySelectInput using the existing FormSelectFieldInput - Create AddressFormFieldInput component - Fix FormSelectFieldInput dropdown + add leftIcon <img width="554" alt="Capture d’écran 2024-12-11 à 15 56 32" src="https://github.com/user-attachments/assets/c3019f29-af76-44e1-96bd-a0c6283674e1" />
This commit is contained in:
@ -1,3 +1,4 @@
|
|||||||
|
import { FormAddressFieldInput } from '@/object-record/record-field/form-types/components/FormAddressFieldInput';
|
||||||
import { FormBooleanFieldInput } from '@/object-record/record-field/form-types/components/FormBooleanFieldInput';
|
import { FormBooleanFieldInput } from '@/object-record/record-field/form-types/components/FormBooleanFieldInput';
|
||||||
import { FormFullNameFieldInput } from '@/object-record/record-field/form-types/components/FormFullNameFieldInput';
|
import { FormFullNameFieldInput } from '@/object-record/record-field/form-types/components/FormFullNameFieldInput';
|
||||||
import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput';
|
import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput';
|
||||||
@ -6,9 +7,11 @@ import { FormTextFieldInput } from '@/object-record/record-field/form-types/comp
|
|||||||
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 { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
import {
|
import {
|
||||||
|
FieldAddressValue,
|
||||||
FieldFullNameValue,
|
FieldFullNameValue,
|
||||||
FieldMetadata,
|
FieldMetadata,
|
||||||
} from '@/object-record/record-field/types/FieldMetadata';
|
} from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress';
|
||||||
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
||||||
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
||||||
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
|
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
|
||||||
@ -57,8 +60,9 @@ export const FormFieldInput = ({
|
|||||||
label={field.label}
|
label={field.label}
|
||||||
defaultValue={defaultValue as string | undefined}
|
defaultValue={defaultValue as string | undefined}
|
||||||
onPersist={onPersist}
|
onPersist={onPersist}
|
||||||
field={field}
|
|
||||||
VariablePicker={VariablePicker}
|
VariablePicker={VariablePicker}
|
||||||
|
options={field.metadata.options}
|
||||||
|
clearLabel={field.label}
|
||||||
/>
|
/>
|
||||||
) : isFieldFullName(field) ? (
|
) : isFieldFullName(field) ? (
|
||||||
<FormFullNameFieldInput
|
<FormFullNameFieldInput
|
||||||
@ -67,5 +71,12 @@ export const FormFieldInput = ({
|
|||||||
onPersist={onPersist}
|
onPersist={onPersist}
|
||||||
VariablePicker={VariablePicker}
|
VariablePicker={VariablePicker}
|
||||||
/>
|
/>
|
||||||
|
) : isFieldAddress(field) ? (
|
||||||
|
<FormAddressFieldInput
|
||||||
|
label={field.label}
|
||||||
|
defaultValue={defaultValue as FieldAddressValue}
|
||||||
|
onPersist={onPersist}
|
||||||
|
VariablePicker={VariablePicker}
|
||||||
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,94 @@
|
|||||||
|
import { FormCountrySelectInput } from '@/object-record/record-field/form-types/components/FormCountrySelectInput';
|
||||||
|
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
|
||||||
|
import { StyledFormCompositeFieldInputContainer } from '@/object-record/record-field/form-types/components/StyledFormCompositeFieldInputContainer';
|
||||||
|
import { StyledFormFieldInputContainer } from '@/object-record/record-field/form-types/components/StyledFormFieldInputContainer';
|
||||||
|
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
|
||||||
|
import { FieldAddressDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
|
||||||
|
import { FieldAddressValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||||
|
|
||||||
|
type FormAddressFieldInputProps = {
|
||||||
|
label?: string;
|
||||||
|
defaultValue: FieldAddressDraftValue | null;
|
||||||
|
onPersist: (value: FieldAddressValue) => void;
|
||||||
|
VariablePicker?: VariablePickerComponent;
|
||||||
|
readonly?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormAddressFieldInput = ({
|
||||||
|
label,
|
||||||
|
defaultValue,
|
||||||
|
onPersist,
|
||||||
|
readonly,
|
||||||
|
VariablePicker,
|
||||||
|
}: FormAddressFieldInputProps) => {
|
||||||
|
const handleChange =
|
||||||
|
(field: keyof FieldAddressDraftValue) => (updatedAddressPart: string) => {
|
||||||
|
const updatedAddress = {
|
||||||
|
addressStreet1: defaultValue?.addressStreet1 ?? '',
|
||||||
|
addressStreet2: defaultValue?.addressStreet2 ?? '',
|
||||||
|
addressCity: defaultValue?.addressCity ?? '',
|
||||||
|
addressState: defaultValue?.addressState ?? '',
|
||||||
|
addressPostcode: defaultValue?.addressPostcode ?? '',
|
||||||
|
addressCountry: defaultValue?.addressCountry ?? '',
|
||||||
|
addressLat: defaultValue?.addressLat ?? null,
|
||||||
|
addressLng: defaultValue?.addressLng ?? null,
|
||||||
|
[field]: updatedAddressPart,
|
||||||
|
};
|
||||||
|
onPersist(updatedAddress);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledFormFieldInputContainer>
|
||||||
|
{label ? <InputLabel>{label}</InputLabel> : null}
|
||||||
|
<StyledFormCompositeFieldInputContainer>
|
||||||
|
<FormTextFieldInput
|
||||||
|
label="Address 1"
|
||||||
|
defaultValue={defaultValue?.addressStreet1 ?? ''}
|
||||||
|
onPersist={handleChange('addressStreet1')}
|
||||||
|
readonly={readonly}
|
||||||
|
VariablePicker={VariablePicker}
|
||||||
|
placeholder="Street address"
|
||||||
|
/>
|
||||||
|
<FormTextFieldInput
|
||||||
|
label="Address 2"
|
||||||
|
defaultValue={defaultValue?.addressStreet2 ?? ''}
|
||||||
|
onPersist={handleChange('addressStreet2')}
|
||||||
|
readonly={readonly}
|
||||||
|
VariablePicker={VariablePicker}
|
||||||
|
placeholder="Street address 2"
|
||||||
|
/>
|
||||||
|
<FormTextFieldInput
|
||||||
|
label="City"
|
||||||
|
defaultValue={defaultValue?.addressCity ?? ''}
|
||||||
|
onPersist={handleChange('addressCity')}
|
||||||
|
readonly={readonly}
|
||||||
|
VariablePicker={VariablePicker}
|
||||||
|
placeholder="City"
|
||||||
|
/>
|
||||||
|
<FormTextFieldInput
|
||||||
|
label="State"
|
||||||
|
defaultValue={defaultValue?.addressState ?? ''}
|
||||||
|
onPersist={handleChange('addressState')}
|
||||||
|
readonly={readonly}
|
||||||
|
VariablePicker={VariablePicker}
|
||||||
|
placeholder="State"
|
||||||
|
/>
|
||||||
|
<FormTextFieldInput
|
||||||
|
label="Post Code"
|
||||||
|
defaultValue={defaultValue?.addressPostcode ?? ''}
|
||||||
|
onPersist={handleChange('addressPostcode')}
|
||||||
|
readonly={readonly}
|
||||||
|
VariablePicker={VariablePicker}
|
||||||
|
placeholder="Post Code"
|
||||||
|
/>
|
||||||
|
<FormCountrySelectInput
|
||||||
|
selectedCountryName={defaultValue?.addressCountry ?? ''}
|
||||||
|
onPersist={handleChange('addressCountry')}
|
||||||
|
readonly={readonly}
|
||||||
|
VariablePicker={VariablePicker}
|
||||||
|
/>
|
||||||
|
</StyledFormCompositeFieldInputContainer>
|
||||||
|
</StyledFormFieldInputContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { IconCircleOff, IconComponentProps } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { FormSelectFieldInput } from '@/object-record/record-field/form-types/components/FormSelectFieldInput';
|
||||||
|
import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent';
|
||||||
|
import { SelectOption } from '@/spreadsheet-import/types';
|
||||||
|
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
||||||
|
|
||||||
|
export const FormCountrySelectInput = ({
|
||||||
|
selectedCountryName,
|
||||||
|
onPersist,
|
||||||
|
readonly = false,
|
||||||
|
VariablePicker,
|
||||||
|
}: {
|
||||||
|
selectedCountryName: string;
|
||||||
|
onPersist: (countryCode: string) => void;
|
||||||
|
readonly?: boolean;
|
||||||
|
VariablePicker?: VariablePickerComponent;
|
||||||
|
}) => {
|
||||||
|
const countries = useCountries();
|
||||||
|
|
||||||
|
const options: SelectOption[] = useMemo(() => {
|
||||||
|
const countryList = countries.map<SelectOption>(
|
||||||
|
({ countryName, Flag }) => ({
|
||||||
|
label: countryName,
|
||||||
|
value: countryName,
|
||||||
|
color: 'transparent',
|
||||||
|
icon: (props: IconComponentProps) =>
|
||||||
|
Flag({ width: props.size, height: props.size }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'No country',
|
||||||
|
value: '',
|
||||||
|
icon: IconCircleOff,
|
||||||
|
},
|
||||||
|
...countryList,
|
||||||
|
];
|
||||||
|
}, [countries]);
|
||||||
|
|
||||||
|
const onChange = (countryCode: string | null) => {
|
||||||
|
if (readonly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countryCode === null) {
|
||||||
|
onPersist('');
|
||||||
|
} else {
|
||||||
|
onPersist(countryCode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormSelectFieldInput
|
||||||
|
label="Country"
|
||||||
|
onPersist={onChange}
|
||||||
|
options={options}
|
||||||
|
defaultValue={selectedCountryName}
|
||||||
|
VariablePicker={VariablePicker}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -3,8 +3,6 @@ import { StyledFormFieldInputInputContainer } from '@/object-record/record-field
|
|||||||
import { StyledFormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/StyledFormFieldInputRowContainer';
|
import { StyledFormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/StyledFormFieldInputRowContainer';
|
||||||
import { VariableChip } from '@/object-record/record-field/form-types/components/VariableChip';
|
import { VariableChip } from '@/object-record/record-field/form-types/components/VariableChip';
|
||||||
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 { FieldSelectMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
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';
|
||||||
@ -21,11 +19,12 @@ import { Key } from 'ts-key-enum';
|
|||||||
import { isDefined, VisibilityHidden } from 'twenty-ui';
|
import { isDefined, VisibilityHidden } from 'twenty-ui';
|
||||||
|
|
||||||
type FormSelectFieldInputProps = {
|
type FormSelectFieldInputProps = {
|
||||||
field: FieldDefinition<FieldSelectMetadata>;
|
|
||||||
label?: string;
|
label?: string;
|
||||||
defaultValue: string | undefined;
|
defaultValue: string | undefined;
|
||||||
onPersist: (value: number | null | string) => void;
|
onPersist: (value: string | null) => void;
|
||||||
VariablePicker?: VariablePickerComponent;
|
VariablePicker?: VariablePickerComponent;
|
||||||
|
options: SelectOption[];
|
||||||
|
clearLabel?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledDisplayModeContainer = styled.button`
|
const StyledDisplayModeContainer = styled.button`
|
||||||
@ -44,12 +43,19 @@ const StyledDisplayModeContainer = styled.button`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledSelectInputContainer = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: ${({ theme }) => theme.spacing(8)};
|
||||||
|
`;
|
||||||
|
|
||||||
export const FormSelectFieldInput = ({
|
export const FormSelectFieldInput = ({
|
||||||
label,
|
label,
|
||||||
field,
|
|
||||||
defaultValue,
|
defaultValue,
|
||||||
onPersist,
|
onPersist,
|
||||||
VariablePicker,
|
VariablePicker,
|
||||||
|
options,
|
||||||
|
clearLabel,
|
||||||
}: FormSelectFieldInputProps) => {
|
}: FormSelectFieldInputProps) => {
|
||||||
const inputId = useId();
|
const inputId = useId();
|
||||||
|
|
||||||
@ -124,7 +130,7 @@ export const FormSelectFieldInput = ({
|
|||||||
onPersist(null);
|
onPersist(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedOption = field.metadata.options.find(
|
const selectedOption = options.find(
|
||||||
(option) => option.value === draftValue.value,
|
(option) => option.value === draftValue.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -193,7 +199,7 @@ export const FormSelectFieldInput = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const optionIds = [
|
const optionIds = [
|
||||||
`No ${field.label}`,
|
`No ${label}`,
|
||||||
...filteredOptions.map((option) => option.value),
|
...filteredOptions.map((option) => option.value),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -215,29 +221,12 @@ export const FormSelectFieldInput = ({
|
|||||||
|
|
||||||
{isDefined(selectedOption) ? (
|
{isDefined(selectedOption) ? (
|
||||||
<SelectDisplay
|
<SelectDisplay
|
||||||
color={selectedOption.color}
|
color={selectedOption.color ?? 'transparent'}
|
||||||
label={selectedOption.label}
|
label={selectedOption.label}
|
||||||
|
Icon={selectedOption.icon ?? undefined}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</StyledDisplayModeContainer>
|
</StyledDisplayModeContainer>
|
||||||
|
|
||||||
{draftValue.editingMode === 'edit' ? (
|
|
||||||
<SelectInput
|
|
||||||
selectableListId={SINGLE_RECORD_SELECT_BASE_LIST}
|
|
||||||
selectableItemIdArray={optionIds}
|
|
||||||
hotkeyScope={hotkeyScope}
|
|
||||||
onEnter={handleSelectEnter}
|
|
||||||
onOptionSelected={handleSubmit}
|
|
||||||
options={field.metadata.options}
|
|
||||||
onCancel={onCancel}
|
|
||||||
defaultOption={selectedOption}
|
|
||||||
onFilterChange={setFilteredOptions}
|
|
||||||
onClear={
|
|
||||||
field.metadata.isNullable ? handleClearField : undefined
|
|
||||||
}
|
|
||||||
clearLabel={field.label}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<VariableChip
|
<VariableChip
|
||||||
@ -246,13 +235,31 @@ export const FormSelectFieldInput = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</StyledFormFieldInputInputContainer>
|
</StyledFormFieldInputInputContainer>
|
||||||
|
<StyledSelectInputContainer>
|
||||||
|
{draftValue.type === 'static' &&
|
||||||
|
draftValue.editingMode === 'edit' && (
|
||||||
|
<SelectInput
|
||||||
|
selectableListId={SINGLE_RECORD_SELECT_BASE_LIST}
|
||||||
|
selectableItemIdArray={optionIds}
|
||||||
|
hotkeyScope={hotkeyScope}
|
||||||
|
onEnter={handleSelectEnter}
|
||||||
|
onOptionSelected={handleSubmit}
|
||||||
|
options={options}
|
||||||
|
onCancel={onCancel}
|
||||||
|
defaultOption={selectedOption}
|
||||||
|
onFilterChange={setFilteredOptions}
|
||||||
|
onClear={handleClearField}
|
||||||
|
clearLabel={clearLabel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StyledSelectInputContainer>
|
||||||
|
|
||||||
{VariablePicker ? (
|
{VariablePicker && (
|
||||||
<VariablePicker
|
<VariablePicker
|
||||||
inputId={inputId}
|
inputId={inputId}
|
||||||
onVariableSelect={handleVariableTagInsert}
|
onVariableSelect={handleVariableTagInsert}
|
||||||
/>
|
/>
|
||||||
) : null}
|
)}
|
||||||
</StyledFormFieldInputRowContainer>
|
</StyledFormFieldInputRowContainer>
|
||||||
</StyledFormFieldInputContainer>
|
</StyledFormFieldInputContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { within } from '@storybook/test';
|
||||||
|
import { FormAddressFieldInput } from '../FormAddressFieldInput';
|
||||||
|
|
||||||
|
const meta: Meta<typeof FormAddressFieldInput> = {
|
||||||
|
title: 'UI/Data/Field/Form/Input/FormAddressFieldInput',
|
||||||
|
component: FormAddressFieldInput,
|
||||||
|
args: {},
|
||||||
|
argTypes: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof FormAddressFieldInput>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
label: 'Address',
|
||||||
|
defaultValue: {
|
||||||
|
addressStreet1: '123 Main St',
|
||||||
|
addressStreet2: 'Apt 123',
|
||||||
|
addressCity: 'Springfield',
|
||||||
|
addressState: 'IL',
|
||||||
|
addressCountry: 'US',
|
||||||
|
addressPostcode: '12345',
|
||||||
|
addressLat: 39.781721,
|
||||||
|
addressLng: -89.650148,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
await canvas.findByText('123 Main St');
|
||||||
|
await canvas.findByText('Address');
|
||||||
|
await canvas.findByText('Post Code');
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { within } from '@storybook/test';
|
||||||
|
import { FormCountrySelectInput } from '../FormCountrySelectInput';
|
||||||
|
|
||||||
|
const meta: Meta<typeof FormCountrySelectInput> = {
|
||||||
|
title: 'UI/Data/Field/Form/Input/FormCountrySelectInput',
|
||||||
|
component: FormCountrySelectInput,
|
||||||
|
args: {},
|
||||||
|
argTypes: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof FormCountrySelectInput>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
selectedCountryName: 'Canada',
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
await canvas.findByText('Country');
|
||||||
|
await canvas.findByText('Canada');
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -60,30 +60,28 @@ export const SelectFieldInput = ({
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<SelectInput
|
||||||
<SelectInput
|
selectableListId={SINGLE_RECORD_SELECT_BASE_LIST}
|
||||||
selectableListId={SINGLE_RECORD_SELECT_BASE_LIST}
|
selectableItemIdArray={optionIds}
|
||||||
selectableItemIdArray={optionIds}
|
hotkeyScope={hotkeyScope}
|
||||||
hotkeyScope={hotkeyScope}
|
onEnter={(itemId) => {
|
||||||
onEnter={(itemId) => {
|
const option = filteredOptions.find(
|
||||||
const option = filteredOptions.find(
|
(option) => option.value === itemId,
|
||||||
(option) => option.value === itemId,
|
);
|
||||||
);
|
if (isDefined(option)) {
|
||||||
if (isDefined(option)) {
|
onSubmit?.(() => persistField(option.value));
|
||||||
onSubmit?.(() => persistField(option.value));
|
resetSelectedItem();
|
||||||
resetSelectedItem();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onOptionSelected={handleSubmit}
|
|
||||||
options={fieldDefinition.metadata.options}
|
|
||||||
onCancel={onCancel}
|
|
||||||
defaultOption={selectedOption}
|
|
||||||
onFilterChange={setFilteredOptions}
|
|
||||||
onClear={
|
|
||||||
fieldDefinition.metadata.isNullable ? handleClearField : undefined
|
|
||||||
}
|
}
|
||||||
clearLabel={fieldDefinition.label}
|
}}
|
||||||
/>
|
onOptionSelected={handleSubmit}
|
||||||
</div>
|
options={fieldDefinition.metadata.options}
|
||||||
|
onCancel={onCancel}
|
||||||
|
defaultOption={selectedOption}
|
||||||
|
onFilterChange={setFilteredOptions}
|
||||||
|
onClear={
|
||||||
|
fieldDefinition.metadata.isNullable ? handleClearField : undefined
|
||||||
|
}
|
||||||
|
clearLabel={fieldDefinition.label}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -94,7 +94,7 @@ export type SelectOption = {
|
|||||||
// Disabled option when already select
|
// Disabled option when already select
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
// Option color
|
// Option color
|
||||||
color?: ThemeColor;
|
color?: ThemeColor | 'transparent';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Input = {
|
export type Input = {
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Tag, ThemeColor } from 'twenty-ui';
|
import { IconComponent, Tag, ThemeColor } from 'twenty-ui';
|
||||||
|
|
||||||
type SelectDisplayProps = {
|
type SelectDisplayProps = {
|
||||||
color: ThemeColor;
|
color: ThemeColor | 'transparent';
|
||||||
label: string;
|
label: string;
|
||||||
|
Icon?: IconComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SelectDisplay = ({ color, label }: SelectDisplayProps) => {
|
export const SelectDisplay = ({ color, label, Icon }: SelectDisplayProps) => {
|
||||||
return <Tag preventShrink color={color} text={label} />;
|
return <Tag preventShrink color={color} text={label} Icon={Icon} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -260,6 +260,7 @@ export const AddressInput = ({
|
|||||||
onFocus={getFocusHandler('addressPostcode')}
|
onFocus={getFocusHandler('addressPostcode')}
|
||||||
/>
|
/>
|
||||||
<CountrySelect
|
<CountrySelect
|
||||||
|
label="COUNTRY"
|
||||||
onChange={getChangeHandler('addressCountry')}
|
onChange={getChangeHandler('addressCountry')}
|
||||||
selectedCountryName={internalValue.addressCountry ?? ''}
|
selectedCountryName={internalValue.addressCountry ?? ''}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -109,7 +109,7 @@ export const SelectInput = ({
|
|||||||
selected={false}
|
selected={false}
|
||||||
text={`No ${clearLabel}`}
|
text={`No ${clearLabel}`}
|
||||||
color="transparent"
|
color="transparent"
|
||||||
variant="outline"
|
variant={'outline'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedOption(undefined);
|
setSelectedOption(undefined);
|
||||||
onClear();
|
onClear();
|
||||||
@ -122,8 +122,9 @@ export const SelectInput = ({
|
|||||||
key={option.value}
|
key={option.value}
|
||||||
selected={selectedOption?.value === option.value}
|
selected={selectedOption?.value === option.value}
|
||||||
text={option.label}
|
text={option.label}
|
||||||
color={option.color as TagColor}
|
color={(option.color as TagColor) ?? 'transparent'}
|
||||||
onClick={() => handleOptionChange(option)}
|
onClick={() => handleOptionChange(option)}
|
||||||
|
LeftIcon={option.icon}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -6,9 +6,11 @@ import { useCountries } from '@/ui/input/components/internal/hooks/useCountries'
|
|||||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||||
|
|
||||||
export const CountrySelect = ({
|
export const CountrySelect = ({
|
||||||
|
label,
|
||||||
selectedCountryName,
|
selectedCountryName,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
|
label: string;
|
||||||
selectedCountryName: string;
|
selectedCountryName: string;
|
||||||
onChange: (countryCode: string) => void;
|
onChange: (countryCode: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
@ -36,7 +38,7 @@ export const CountrySelect = ({
|
|||||||
fullWidth
|
fullWidth
|
||||||
dropdownId={SELECT_COUNTRY_DROPDOWN_ID}
|
dropdownId={SELECT_COUNTRY_DROPDOWN_ID}
|
||||||
options={options}
|
options={options}
|
||||||
label="COUNTRY"
|
label={label}
|
||||||
withSearchInput
|
withSearchInput
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
value={selectedCountryName}
|
value={selectedCountryName}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ const StyledTag = styled.h3<{
|
|||||||
border-radius: ${BORDER_COMMON.radius.sm};
|
border-radius: ${BORDER_COMMON.radius.sm};
|
||||||
color: ${({ color, theme }) =>
|
color: ${({ color, theme }) =>
|
||||||
color === 'transparent'
|
color === 'transparent'
|
||||||
? theme.font.color.tertiary
|
? theme.font.color.secondary
|
||||||
: theme.tag.text[color]};
|
: theme.tag.text[color]};
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
font-size: ${({ theme }) => theme.font.size.md};
|
font-size: ${({ theme }) => theme.font.size.md};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useTheme } from '@emotion/react';
|
|||||||
|
|
||||||
import { StyledMenuItemLeftContent } from '../internals/components/StyledMenuItemBase';
|
import { StyledMenuItemLeftContent } from '../internals/components/StyledMenuItemBase';
|
||||||
|
|
||||||
import { IconCheck, Tag } from '@ui/display';
|
import { IconCheck, IconComponent, Tag } from '@ui/display';
|
||||||
import { ThemeColor } from '@ui/theme';
|
import { ThemeColor } from '@ui/theme';
|
||||||
import { StyledMenuItemSelect } from './MenuItemSelect';
|
import { StyledMenuItemSelect } from './MenuItemSelect';
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ type MenuItemSelectTagProps = {
|
|||||||
color: ThemeColor | 'transparent';
|
color: ThemeColor | 'transparent';
|
||||||
text: string;
|
text: string;
|
||||||
variant?: 'solid' | 'outline';
|
variant?: 'solid' | 'outline';
|
||||||
|
LeftIcon?: IconComponent | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MenuItemSelectTag = ({
|
export const MenuItemSelectTag = ({
|
||||||
@ -24,9 +25,9 @@ export const MenuItemSelectTag = ({
|
|||||||
onClick,
|
onClick,
|
||||||
text,
|
text,
|
||||||
variant = 'solid',
|
variant = 'solid',
|
||||||
|
LeftIcon,
|
||||||
}: MenuItemSelectTagProps) => {
|
}: MenuItemSelectTagProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledMenuItemSelect
|
<StyledMenuItemSelect
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
@ -35,7 +36,12 @@ export const MenuItemSelectTag = ({
|
|||||||
isKeySelected={isKeySelected}
|
isKeySelected={isKeySelected}
|
||||||
>
|
>
|
||||||
<StyledMenuItemLeftContent>
|
<StyledMenuItemLeftContent>
|
||||||
<Tag variant={variant} color={color} text={text} />
|
<Tag
|
||||||
|
variant={variant}
|
||||||
|
color={color}
|
||||||
|
text={text}
|
||||||
|
Icon={LeftIcon ?? undefined}
|
||||||
|
/>
|
||||||
</StyledMenuItemLeftContent>
|
</StyledMenuItemLeftContent>
|
||||||
{selected && <IconCheck size={theme.icon.size.sm} />}
|
{selected && <IconCheck size={theme.icon.size.sm} />}
|
||||||
</StyledMenuItemSelect>
|
</StyledMenuItemSelect>
|
||||||
|
|||||||
Reference in New Issue
Block a user