fix: settings form select menu (#9179)
Closes: #8647 Closes: #8649 **Changes & Why** 1. Added a Search Input to `SettingsDataModelFieldAddressForm` & `SettingsDataModelFieldCurrencyForm` as `Select` component already accepts it as a prop. 2. Gave a fixed width to the dropdown of both the above components to ensure it doesn't shrink on search for the menu items with low word count. 3. Added countries Flag to `SettingsDataModelFieldAddressForm`. 4. Replaced `MenuItem` with `MenuItemSelect` to get the desired highlighted background for the selected item with `IconCheck` to differentiate the current selected item. This is useful across all the select components throughout the app. 5. I realized that in some components we might not need IconCheck and only need a highlighted background for the selected item. For ex: `SettingsDataModelFieldBooleanForm` . Therefore, I created a prop `needIconCheck` with default as true so it doesn't break the existing `MenuItemSelect` and we can pass that prop as false wherever needed. [Screencast from 2024-12-21 12-08-08.webm](https://github.com/user-attachments/assets/4f8070a8-f339-4556-a137-bbbad58b171c)
This commit is contained in:
@ -4,8 +4,8 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
|||||||
import { addressSchema as addressFieldDefaultValueSchema } from '@/object-record/record-field/types/guards/isFieldAddressValue';
|
import { addressSchema as addressFieldDefaultValueSchema } from '@/object-record/record-field/types/guards/isFieldAddressValue';
|
||||||
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
||||||
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
||||||
import { Select } from '@/ui/input/components/Select';
|
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||||
import { IconMap } from 'twenty-ui';
|
import { IconCircleOff, IconComponentProps, IconMap } from 'twenty-ui';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
|
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
|
||||||
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||||
@ -33,13 +33,16 @@ export const SettingsDataModelFieldAddressForm = ({
|
|||||||
const { control } = useFormContext<SettingsDataModelFieldTextFormValues>();
|
const { control } = useFormContext<SettingsDataModelFieldTextFormValues>();
|
||||||
const countries = useCountries()
|
const countries = useCountries()
|
||||||
.sort((a, b) => a.countryName.localeCompare(b.countryName))
|
.sort((a, b) => a.countryName.localeCompare(b.countryName))
|
||||||
.map((country) => ({
|
.map<SelectOption<string>>(({ countryName, Flag }) => ({
|
||||||
label: country.countryName,
|
label: countryName,
|
||||||
value: country.countryName,
|
value: countryName,
|
||||||
|
Icon: (props: IconComponentProps) =>
|
||||||
|
Flag({ width: props.size, height: props.size }),
|
||||||
}));
|
}));
|
||||||
countries.unshift({
|
countries.unshift({
|
||||||
label: 'No country',
|
label: 'No country',
|
||||||
value: '',
|
value: '',
|
||||||
|
Icon: IconCircleOff,
|
||||||
});
|
});
|
||||||
const defaultDefaultValue = {
|
const defaultDefaultValue = {
|
||||||
addressStreet1: "''",
|
addressStreet1: "''",
|
||||||
@ -69,7 +72,7 @@ export const SettingsDataModelFieldAddressForm = ({
|
|||||||
description="The default country for new addresses"
|
description="The default country for new addresses"
|
||||||
>
|
>
|
||||||
<Select<string>
|
<Select<string>
|
||||||
dropdownWidth={'auto'}
|
dropdownWidth={220}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
dropdownId="selectDefaultCountry"
|
dropdownId="selectDefaultCountry"
|
||||||
value={stripSimpleQuotesFromString(defaultCountry)}
|
value={stripSimpleQuotesFromString(defaultCountry)}
|
||||||
@ -81,6 +84,7 @@ export const SettingsDataModelFieldAddressForm = ({
|
|||||||
}
|
}
|
||||||
options={countries}
|
options={countries}
|
||||||
selectSizeVariant="small"
|
selectSizeVariant="small"
|
||||||
|
withSearchInput={true}
|
||||||
/>
|
/>
|
||||||
</SettingsOptionCardContentSelect>
|
</SettingsOptionCardContentSelect>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export const SettingsDataModelFieldBooleanForm = ({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
dropdownId="object-field-default-value-select-boolean"
|
dropdownId="object-field-default-value-select-boolean"
|
||||||
dropdownWidth={120}
|
dropdownWidth={120}
|
||||||
|
needIconCheck={false}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
value: true,
|
value: true,
|
||||||
|
|||||||
@ -60,13 +60,14 @@ export const SettingsDataModelFieldCurrencyForm = ({
|
|||||||
description="Choose the default currency that will apply"
|
description="Choose the default currency that will apply"
|
||||||
>
|
>
|
||||||
<Select<string>
|
<Select<string>
|
||||||
dropdownWidth={'auto'}
|
dropdownWidth={220}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
dropdownId="object-field-default-value-select-currency"
|
dropdownId="object-field-default-value-select-currency"
|
||||||
options={OPTIONS}
|
options={OPTIONS}
|
||||||
selectSizeVariant="small"
|
selectSizeVariant="small"
|
||||||
|
withSearchInput={true}
|
||||||
/>
|
/>
|
||||||
</SettingsOptionCardContentSelect>
|
</SettingsOptionCardContentSelect>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -59,6 +59,7 @@ export const SettingsDataModelFieldNumberForm = ({
|
|||||||
value={type}
|
value={type}
|
||||||
onChange={(value) => onChange({ type: value, decimals: count })}
|
onChange={(value) => onChange({ type: value, decimals: count })}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
needIconCheck={false}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
Icon: IconNumber9,
|
Icon: IconNumber9,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { MouseEvent, useMemo, useRef, useState } from 'react';
|
import { MouseEvent, useMemo, useRef, useState } from 'react';
|
||||||
import { IconComponent, MenuItem } from 'twenty-ui';
|
import { IconComponent, MenuItem, MenuItemSelect } from 'twenty-ui';
|
||||||
|
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
@ -43,6 +43,7 @@ export type SelectProps<Value extends SelectValue> = {
|
|||||||
options: SelectOption<Value>[];
|
options: SelectOption<Value>[];
|
||||||
value?: Value;
|
value?: Value;
|
||||||
withSearchInput?: boolean;
|
withSearchInput?: boolean;
|
||||||
|
needIconCheck?: boolean;
|
||||||
callToActionButton?: CallToActionButton;
|
callToActionButton?: CallToActionButton;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ export const Select = <Value extends SelectValue>({
|
|||||||
options,
|
options,
|
||||||
value,
|
value,
|
||||||
withSearchInput,
|
withSearchInput,
|
||||||
|
needIconCheck,
|
||||||
callToActionButton,
|
callToActionButton,
|
||||||
}: SelectProps<Value>) => {
|
}: SelectProps<Value>) => {
|
||||||
const selectContainerRef = useRef<HTMLDivElement>(null);
|
const selectContainerRef = useRef<HTMLDivElement>(null);
|
||||||
@ -148,10 +150,12 @@ export const Select = <Value extends SelectValue>({
|
|||||||
{!!filteredOptions.length && (
|
{!!filteredOptions.length && (
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
{filteredOptions.map((option) => (
|
{filteredOptions.map((option) => (
|
||||||
<MenuItem
|
<MenuItemSelect
|
||||||
key={`${option.value}-${option.label}`}
|
key={`${option.value}-${option.label}`}
|
||||||
LeftIcon={option.Icon}
|
LeftIcon={option.Icon}
|
||||||
text={option.label}
|
text={option.label}
|
||||||
|
selected={selectedOption.value === option.value}
|
||||||
|
needIconCheck={needIconCheck}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onChange?.(option.value);
|
onChange?.(option.value);
|
||||||
onBlur?.();
|
onBlur?.();
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export const StyledMenuItemSelect = styled(StyledMenuItemBase)<{
|
|||||||
type MenuItemSelectProps = {
|
type MenuItemSelectProps = {
|
||||||
LeftIcon?: IconComponent | null | undefined;
|
LeftIcon?: IconComponent | null | undefined;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
|
needIconCheck?: boolean;
|
||||||
text: string;
|
text: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@ -52,6 +53,7 @@ export const MenuItemSelect = ({
|
|||||||
LeftIcon,
|
LeftIcon,
|
||||||
text,
|
text,
|
||||||
selected,
|
selected,
|
||||||
|
needIconCheck = true,
|
||||||
className,
|
className,
|
||||||
onClick,
|
onClick,
|
||||||
disabled,
|
disabled,
|
||||||
@ -69,7 +71,7 @@ export const MenuItemSelect = ({
|
|||||||
hovered={hovered}
|
hovered={hovered}
|
||||||
>
|
>
|
||||||
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} />
|
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} />
|
||||||
{selected && <IconCheck size={theme.icon.size.md} />}
|
{selected && needIconCheck && <IconCheck size={theme.icon.size.md} />}
|
||||||
{hasSubMenu && (
|
{hasSubMenu && (
|
||||||
<IconChevronRight
|
<IconChevronRight
|
||||||
size={theme.icon.size.sm}
|
size={theme.icon.size.sm}
|
||||||
|
|||||||
Reference in New Issue
Block a user