diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx
index 5ab4617ed..eff716fb1 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx
@@ -1,5 +1,5 @@
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
-import { TextInput } from '@/ui/field/input/components/TextInput';
+import { CurrencyInput } from '@/ui/field/input/components/CurrencyInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
import { useCurrencyField } from '../../hooks/useCurrencyField';
@@ -79,10 +79,18 @@ export const CurrencyFieldInput = ({
});
};
+ const handleSelect = (newValue: string) => {
+ setDraftValue({
+ amount: draftValue?.amount ?? '',
+ currencyCode: newValue as CurrencyCode,
+ });
+ };
+
return (
-
);
diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts
index ee11e822c..72c024f7a 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts
@@ -1,4 +1,3 @@
-import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
@@ -31,7 +30,7 @@ export const computeDraftValueFromFieldValue = ({
return {
amount: fieldValue?.amountMicros ? fieldValue.amountMicros / 1000000 : '',
- currenyCode: CurrencyCode.USD,
+ currencyCode: fieldValue?.currencyCode ?? '',
} as unknown as FieldInputDraftValue;
}
if (isFieldRelation(fieldDefinition)) {
diff --git a/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx
new file mode 100644
index 000000000..8a72584b5
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx
@@ -0,0 +1,152 @@
+import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
+import { useTheme } from '@emotion/react';
+import styled from '@emotion/styled';
+
+import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
+import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes';
+import { IconComponent } from '@/ui/display/icon/types/IconComponent';
+import { CurrencyPickerDropdownButton } from '@/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton';
+import { TEXT_INPUT_STYLE } from '@/ui/theme/constants/TextInputStyle';
+
+export const StyledInput = styled.input`
+ margin: 0;
+ ${TEXT_INPUT_STYLE}
+ width: 100%;
+ padding: ${({ theme }) => `${theme.spacing(0)} ${theme.spacing(1)}`};
+`;
+
+const StyledContainer = styled.div`
+ align-items: center;
+
+ border: none;
+ border-radius: ${({ theme }) => theme.border.radius.sm};
+ box-shadow: ${({ theme }) => theme.boxShadow.strong};
+
+ display: flex;
+ justify-content: center;
+`;
+
+const StyledIcon = styled.div`
+ align-items: center;
+ display: flex;
+
+ & > svg {
+ padding-left: ${({ theme }) => theme.spacing(1)};
+ color: ${({ theme }) => theme.font.color.tertiary};
+ height: ${({ theme }) => theme.icon.size.md}px;
+ width: ${({ theme }) => theme.icon.size.md}px;
+ }
+`;
+
+export type CurrencyInputProps = {
+ placeholder?: string;
+ autoFocus?: boolean;
+ value: string;
+ currencyCode: string;
+ onEnter: (newText: string) => void;
+ onEscape: (newText: string) => void;
+ onTab?: (newText: string) => void;
+ onShiftTab?: (newText: string) => void;
+ onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void;
+ onChange?: (newText: string) => void;
+ onSelect?: (newText: string) => void;
+ hotkeyScope: string;
+};
+
+type Currency = {
+ label: string;
+ value: string;
+ Icon: any;
+};
+
+export const CurrencyInput = ({
+ autoFocus,
+ value,
+ currencyCode,
+ placeholder,
+ onEnter,
+ onEscape,
+ onTab,
+ onShiftTab,
+ onClickOutside,
+ onChange,
+ onSelect,
+ hotkeyScope,
+}: CurrencyInputProps) => {
+ const theme = useTheme();
+
+ const [internalText, setInternalText] = useState(value);
+ const [internalCurrency, setInternalCurrency] = useState(
+ null,
+ );
+
+ const wrapperRef = useRef(null);
+
+ const handleChange = (event: ChangeEvent) => {
+ setInternalText(event.target.value);
+ onChange?.(event.target.value);
+ };
+
+ const handleCurrencyChange = (currency: Currency) => {
+ setInternalCurrency(currency);
+ onSelect?.(currency.value);
+ };
+
+ useRegisterInputEvents({
+ inputRef: wrapperRef,
+ inputValue: internalText,
+ onEnter,
+ onEscape,
+ onClickOutside,
+ onTab,
+ onShiftTab,
+ hotkeyScope,
+ });
+
+ const currencies = useMemo(
+ () =>
+ Object.entries(SETTINGS_FIELD_CURRENCY_CODES).map(
+ ([key, { Icon, label }]) => ({
+ value: key,
+ Icon,
+ label,
+ }),
+ ),
+ [],
+ );
+
+ useEffect(() => {
+ const currency = currencies.find(({ value }) => value === currencyCode);
+ if (currency) {
+ setInternalCurrency(currency);
+ }
+ }, [currencies, currencyCode]);
+
+ useEffect(() => {
+ setInternalText(value);
+ }, [value]);
+
+ const Icon: IconComponent = internalCurrency?.Icon;
+
+ return (
+
+
+
+ {Icon && (
+
+ )}
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx
new file mode 100644
index 000000000..305ca9851
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx
@@ -0,0 +1,108 @@
+import { useEffect, useState } from 'react';
+import { useTheme } from '@emotion/react';
+import styled from '@emotion/styled';
+
+import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
+import { IconChevronDown } from '@/ui/display/icon';
+import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
+import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
+
+import { CurrencyPickerHotkeyScope } from '../types/CurrencyPickerHotkeyScope';
+
+import { CurrencyPickerDropdownSelect } from './CurrencyPickerDropdownSelect';
+
+type StyledDropdownButtonProps = {
+ isUnfolded: boolean;
+};
+
+export const StyledDropdownButtonContainer = styled.div`
+ align-items: center;
+ color: ${({ color }) => color ?? 'none'};
+ cursor: pointer;
+ display: flex;
+ border-right: ${({ theme }) => `1px solid ${theme.border.color.medium}`};
+ height: 32px;
+ padding-left: ${({ theme }) => theme.spacing(2)};
+ padding-right: ${({ theme }) => theme.spacing(2)};
+ user-select: none;
+ &:hover {
+ filter: brightness(0.95);
+ }
+`;
+
+const StyledIconContainer = styled.div`
+ align-items: center;
+ color: ${({ theme }) => theme.font.color.tertiary};
+ display: flex;
+ gap: ${({ theme }) => theme.spacing(1)};
+ font-weight: ${({ theme }) => theme.font.weight.medium};
+ justify-content: center;
+
+ svg {
+ align-items: center;
+ display: flex;
+ height: 16px;
+ justify-content: center;
+ }
+`;
+
+export type Currency = {
+ label: string;
+ value: string;
+ Icon: any;
+};
+
+export const CurrencyPickerDropdownButton = ({
+ valueCode,
+ onChange,
+ currencies,
+}: {
+ valueCode: string;
+ onChange: (currency: Currency) => void;
+ currencies: Currency[];
+}) => {
+ const theme = useTheme();
+
+ const [selectedCurrency, setSelectedCurrency] = useState();
+
+ const { isDropdownOpen, closeDropdown } = useDropdown(
+ CurrencyPickerHotkeyScope.CurrencyPicker,
+ );
+
+ const handleChange = (currency: Currency) => {
+ onChange(currency);
+ closeDropdown();
+ };
+
+ useEffect(() => {
+ const currency = currencies.find(({ value }) => value === valueCode);
+ if (currency) {
+ setSelectedCurrency(currency);
+ }
+ }, [valueCode, currencies]);
+
+ return (
+
+
+ {selectedCurrency ? selectedCurrency.value : CurrencyCode.USD}
+
+
+
+ }
+ dropdownComponents={
+
+ }
+ dropdownPlacement="bottom-start"
+ dropdownOffset={{ x: 0, y: 4 }}
+ />
+ );
+};
diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownSelect.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownSelect.tsx
new file mode 100644
index 000000000..98f4348f0
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownSelect.tsx
@@ -0,0 +1,71 @@
+import { useMemo, useState } from 'react';
+
+import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
+import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
+import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
+import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
+import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
+import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar';
+
+import { Currency } from './CurrencyPickerDropdownButton';
+
+export const CurrencyPickerDropdownSelect = ({
+ currencies,
+ selectedCurrency,
+ onChange,
+}: {
+ currencies: Currency[];
+ selectedCurrency?: Currency;
+ onChange: (currency: Currency) => void;
+}) => {
+ const [searchFilter, setSearchFilter] = useState('');
+
+ const filteredCurrencies = useMemo(
+ () =>
+ currencies.filter(
+ ({ value, label }) =>
+ value
+ .toLocaleLowerCase()
+ .includes(searchFilter.toLocaleLowerCase()) ||
+ label.toLocaleLowerCase().includes(searchFilter.toLocaleLowerCase()),
+ ),
+ [currencies, searchFilter],
+ );
+
+ return (
+
+ setSearchFilter(event.target.value)}
+ autoFocus
+ />
+
+
+ {filteredCurrencies.length === 0 ? (
+
+ ) : (
+ <>
+ {selectedCurrency && (
+ onChange(selectedCurrency)}
+ text={`${selectedCurrency.label} (${selectedCurrency.value})`}
+ />
+ )}
+ {filteredCurrencies.map((item) =>
+ selectedCurrency?.value === item.value ? null : (
+ onChange(item)}
+ text={`${item.label} (${item.value})`}
+ />
+ ),
+ )}
+ >
+ )}
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/currency/types/CurrencyPickerHotkeyScope.ts b/packages/twenty-front/src/modules/ui/input/components/internal/currency/types/CurrencyPickerHotkeyScope.ts
new file mode 100644
index 000000000..cfeaebd04
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/input/components/internal/currency/types/CurrencyPickerHotkeyScope.ts
@@ -0,0 +1,3 @@
+export enum CurrencyPickerHotkeyScope {
+ CurrencyPicker = 'currency-picker',
+}
diff --git a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelectAvatar.tsx b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelectAvatar.tsx
index 59026208d..14756606e 100644
--- a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelectAvatar.tsx
+++ b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelectAvatar.tsx
@@ -12,7 +12,7 @@ import {
import { StyledMenuItemSelect } from './MenuItemSelect';
type MenuItemSelectAvatarProps = {
- avatar: ReactNode;
+ avatar?: ReactNode;
selected: boolean;
text: string;
className?: string;