diff --git a/packages/ffe-account-selector-react/src/account-selector/AccountSelector.mdx b/packages/ffe-account-selector-react/src/account-selector/AccountSelector.mdx
index dfaa940df5..031c505c91 100644
--- a/packages/ffe-account-selector-react/src/account-selector/AccountSelector.mdx
+++ b/packages/ffe-account-selector-react/src/account-selector/AccountSelector.mdx
@@ -35,3 +35,7 @@ Dersom du ønsker å skjule kontodetaljer, kan du bruke `hideAccountDetails`.
Dersom du ønsker å ha ekstra tekst på bunnen av lista, kan du bruke `postListElement`.
+
+Dersom du ønsker å vise en annen informasjon enn kontonavn i inputen, så kan du velge hvilken attribute som brukes med `displayAttribute`.
+
+
diff --git a/packages/ffe-account-selector-react/src/account-selector/AccountSelector.spec.tsx b/packages/ffe-account-selector-react/src/account-selector/AccountSelector.spec.tsx
index 38c5e9fed8..85c21be46e 100644
--- a/packages/ffe-account-selector-react/src/account-selector/AccountSelector.spec.tsx
+++ b/packages/ffe-account-selector-react/src/account-selector/AccountSelector.spec.tsx
@@ -618,4 +618,70 @@ describe('', () => {
expect(a11yStatusMessage).toHaveTextContent('');
jest.useRealTimers();
});
+
+ it('allows passing a custom display attribute', async () => {
+ const handleAccountSelectedMock = jest.fn();
+
+ render(
+ ,
+ );
+
+ const input = screen.getByRole('combobox');
+
+ expect(input.getAttribute('value')).toBe('');
+ fireEvent.change(input, { target: { value: 'Gr' } });
+
+ fireEvent.click(screen.getByText('Gris'));
+
+ expect(handleAccountSelectedMock).toHaveBeenCalledTimes(1);
+ expect(handleAccountSelectedMock).toHaveBeenCalledWith(accounts[3]);
+ expect(input.getAttribute('value')).toEqual('1253 47 789102');
+ });
+
+ it('passing displayAttribute should make it searchable', async () => {
+ type FunkyAccounts = Account & { funkySmell: string };
+ const funkyAccounts: FunkyAccounts[] = accounts.map((account, idx) => ({
+ ...account,
+ funkySmell: `Smells like money${idx}`,
+ }));
+
+ render(
+
+ id="id"
+ labelledById="labelId"
+ accounts={funkyAccounts}
+ displayAttribute={'funkySmell'}
+ locale="nb"
+ onAccountSelected={handleAccountSelected}
+ onReset={onReset}
+ selectedAccount={funkyAccounts[0]}
+ ariaInvalid={false}
+ />,
+ );
+
+ const input = screen.getByRole('combobox');
+ fireEvent.click(input);
+
+ expect(screen.queryByText('Ingen samsvarende konto')).toBeNull();
+ fireEvent.change(input, {
+ target: { value: 'Dette skal få ingen match' },
+ });
+ expect(
+ screen.queryByText('Ingen samsvarende konto'),
+ ).toBeInTheDocument();
+
+ fireEvent.change(input, { target: { value: 'money3' } });
+ fireEvent.click(screen.getByText('Gris'));
+ expect(input.getAttribute('value')).toEqual('Smells like money3');
+ });
});
diff --git a/packages/ffe-account-selector-react/src/account-selector/AccountSelector.stories.tsx b/packages/ffe-account-selector-react/src/account-selector/AccountSelector.stories.tsx
index 1c1f457879..905c0a322b 100644
--- a/packages/ffe-account-selector-react/src/account-selector/AccountSelector.stories.tsx
+++ b/packages/ffe-account-selector-react/src/account-selector/AccountSelector.stories.tsx
@@ -3,6 +3,7 @@ import { AccountSelector } from './AccountSelector';
import { InputGroup } from '@sb1/ffe-form-react';
import type { StoryObj, Meta } from '@storybook/react';
import { SmallText } from '@sb1/ffe-core-react';
+import { accountFormatter } from '../format';
const meta: Meta = {
title: 'Komponenter/Account-selector/AccountSelector',
@@ -254,3 +255,41 @@ export const InitialValue: Story = {
);
},
};
+
+type PrettyAccount = Account & { prettyName: string };
+
+const prettyAccounts: PrettyAccount[] = accounts.map(account => ({
+ ...account,
+ prettyName: `${account.name} - ${accountFormatter(account.accountNumber)}`,
+}));
+export const CustomDisplayAttribute: StoryObj<
+ typeof AccountSelector
+> = {
+ args: {
+ id: 'input-id',
+ labelledById: 'label-id',
+ locale: 'nb',
+ formatAccountNumber: true,
+ allowCustomAccount: false,
+ displayAttribute: 'prettyName',
+ accounts: prettyAccounts,
+ },
+ render: function Render(args) {
+ const [selectedAccount, setSelectedAccount] = useState(
+ prettyAccounts[2],
+ );
+ return (
+
+
+ {...args}
+ selectedAccount={selectedAccount}
+ onAccountSelected={setSelectedAccount}
+ />
+
+ );
+ },
+};
diff --git a/packages/ffe-account-selector-react/src/account-selector/AccountSelector.tsx b/packages/ffe-account-selector-react/src/account-selector/AccountSelector.tsx
index e2d6ecb0c7..eb41191f7a 100644
--- a/packages/ffe-account-selector-react/src/account-selector/AccountSelector.tsx
+++ b/packages/ffe-account-selector-react/src/account-selector/AccountSelector.tsx
@@ -40,6 +40,8 @@ export interface AccountSelectorProps {
formatAccountNumber?: boolean;
/** id of element that labels input field */
labelledById?: string;
+ /** Attribute used in the input when an item is selected. **/
+ displayAttribute?: keyof T;
/**
* Allows selecting the text the user writes even if it does not match anything in the accounts array.
* Useful e.g. if you want to pay to account that is not in yur recipients list.
@@ -91,6 +93,7 @@ export const AccountSelector = ({
ariaInvalid,
onOpen,
onClose,
+ displayAttribute,
...rest
}: AccountSelectorProps) => {
const [inputValue, setInputValue] = useState(selectedAccount?.name || '');
@@ -119,6 +122,7 @@ export const AccountSelector = ({
onAccountSelected({
name: value.name,
accountNumber: value.name,
+ ...(displayAttribute ? { [displayAttribute]: value.name } : {}),
} as T);
setInputValue(value.name);
} else {
@@ -137,6 +141,7 @@ export const AccountSelector = ({
id={id}
labelledById={labelledById}
+ displayAttribute={displayAttribute}
inputProps={{
...inputProps,
onChange: onInputChange,
@@ -165,6 +170,13 @@ export const AccountSelector = ({
? formatter(inputValue)
: inputValue,
accountNumber: '',
+ ...(displayAttribute
+ ? {
+ [displayAttribute]: formatter
+ ? formatter(inputValue)
+ : inputValue,
+ }
+ : {}),
} as T,
],
}
@@ -172,7 +184,11 @@ export const AccountSelector = ({
}
formatter={formatter}
onChange={handleAccountSelected}
- searchAttributes={['name', 'accountNumber']}
+ searchAttributes={[
+ 'name',
+ 'accountNumber',
+ ...(displayAttribute ? [displayAttribute] : []),
+ ]}
locale={locale}
optionBody={({ item, isHighlighted, ...restOptionBody }) => {
if (OptionBody) {
diff --git a/packages/ffe-searchable-dropdown-react/src/getListToRender.ts b/packages/ffe-searchable-dropdown-react/src/getListToRender.ts
index e742e45409..15d006a613 100644
--- a/packages/ffe-searchable-dropdown-react/src/getListToRender.ts
+++ b/packages/ffe-searchable-dropdown-react/src/getListToRender.ts
@@ -35,7 +35,7 @@ export const getListToRender = - >({
searchMatcher?: SearchMatcher
- ;
showAllItemsInDropdown: boolean;
}): { noMatch: boolean; listToRender: Item[] } => {
- const trimmedInput = inputValue ? inputValue.trim() : '';
+ const trimmedInput = inputValue ? String(inputValue).trim() : '';
const shouldFilter = trimmedInput.length > 0;
diff --git a/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.spec.tsx b/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.spec.tsx
index 4ec07f2dd0..044f5d422f 100644
--- a/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.spec.tsx
+++ b/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.spec.tsx
@@ -1026,4 +1026,33 @@ describe('SearchableDropdown', () => {
await user.click(input);
await screen.findByText('Dette er et postListElement!');
});
+
+ it('allows passing a custom display attribute', async () => {
+ const onChange = jest.fn();
+ const user = userEvent.setup();
+
+ render(
+ ,
+ );
+
+ const input = screen.getByRole('combobox');
+
+ expect(input.getAttribute('value')).toBe('');
+ await user.type(input, 'Be');
+
+ await user.click(screen.getByText('Beslag skytter'));
+
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledWith(companies[2]);
+ expect(input.getAttribute('value')).toEqual('812602552');
+ });
});
diff --git a/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.stories.tsx b/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.stories.tsx
index 6785ee2b41..f78b7510bd 100644
--- a/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.stories.tsx
+++ b/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.stories.tsx
@@ -227,3 +227,27 @@ export const PostListElement: Story = {
);
},
};
+
+export const CustomDisplayAttribute: Story = {
+ args: {
+ ...Standard.args,
+ displayAttribute: 'organizationNumber',
+ dropdownAttributes: ['organizationName', 'organizationNumber'],
+ searchAttributes: ['organizationNumber', 'organizationName'],
+ },
+ render: function Render({ id, labelledById, ...args }) {
+ return (
+
+
+
+ );
+ },
+};
diff --git a/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.tsx b/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.tsx
index 48a6d53995..0076ba7fb1 100644
--- a/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.tsx
+++ b/packages/ffe-searchable-dropdown-react/src/single/SearchableDropdown.tsx
@@ -51,6 +51,8 @@ export interface SearchableDropdownProps
- > {
dropdownAttributes: (keyof Item)[];
/** Array of attributes used when filtering search */
searchAttributes: (keyof Item)[];
+ /** Attribute used in the input when an item is selected. Defaults to first in searchAttributes **/
+ displayAttribute?: keyof Item;
/** Props used on input field */
inputProps?: React.ComponentProps<'input'>;
/** Limits number of rendered dropdown elements */
@@ -105,6 +107,7 @@ function SearchableDropdownWithForwardRef
- >(
dropdownList,
dropdownAttributes,
searchAttributes,
+ displayAttribute = searchAttributes[0],
maxRenderedDropdownElements = Number.MAX_SAFE_INTEGER,
onChange,
inputProps,
@@ -127,6 +130,7 @@ function SearchableDropdownWithForwardRef
- >(
const [state, dispatch] = useReducer(
createReducer({
dropdownList,
+ displayAttribute: displayAttribute,
searchAttributes,
maxRenderedDropdownElements,
noMatchDropdownList: noMatch?.dropdownList,
@@ -137,7 +141,8 @@ function SearchableDropdownWithForwardRef
- >(
isExpanded: false,
selectedItems: [],
highlightedIndex: -1,
- inputValue: selectedItem ? selectedItem[dropdownAttributes[0]] : '',
+ formattedInputValue: '',
+ inputValue: selectedItem ? selectedItem[displayAttribute] : '',
},
initialState => {
return {
@@ -189,7 +194,7 @@ function SearchableDropdownWithForwardRef
- >(
isLoading,
locale,
resultCount: state.listToRender.length,
- selectedValue: state.selectedItem?.[searchAttributes[0]],
+ selectedValue: state.selectedItem?.[displayAttribute],
});
useLayoutEffect(() => {
diff --git a/packages/ffe-searchable-dropdown-react/src/single/reducer.ts b/packages/ffe-searchable-dropdown-react/src/single/reducer.ts
index efe0c6fc8d..8b85f51bca 100644
--- a/packages/ffe-searchable-dropdown-react/src/single/reducer.ts
+++ b/packages/ffe-searchable-dropdown-react/src/single/reducer.ts
@@ -24,6 +24,7 @@ export const createReducer =
- >({
searchAttributes,
dropdownList,
+ displayAttribute,
noMatchDropdownList,
maxRenderedDropdownElements,
searchMatcher,
@@ -31,6 +32,7 @@ export const createReducer =
}: {
dropdownList: Item[];
searchAttributes: Array;
+ displayAttribute: keyof Item;
noMatchDropdownList: Item[] | undefined;
maxRenderedDropdownElements: number;
searchMatcher: SearchMatcher
- | undefined;
@@ -38,16 +40,17 @@ export const createReducer =
}) =>
(state: State
- , action: Action
- ): State
- => {
switch (action.type) {
- case 'InputKeyDownEscape':
+ case 'InputKeyDownEscape': {
return {
...state,
noMatch: false,
isExpanded: false,
highlightedIndex: -1,
inputValue: state.selectedItem
- ? state.selectedItem[searchAttributes[0]]
+ ? state.selectedItem[displayAttribute]
: '',
};
+ }
case 'InputClick': {
const { noMatch, listToRender } = getListToRender({
inputValue: state.inputValue,
@@ -90,23 +93,24 @@ export const createReducer =
noMatch,
};
}
- case 'ToggleButtonPressed':
+ case 'ToggleButtonPressed': {
return {
...state,
isExpanded: !state.isExpanded,
};
+ }
case 'ItemSelectedProgrammatically':
case 'ItemOnClick':
- case 'InputKeyDownEnter':
+ case 'InputKeyDownEnter': {
return {
...state,
isExpanded: false,
highlightedIndex: -1,
selectedItem: action.payload?.selectedItem,
inputValue:
- action.payload?.selectedItem?.[searchAttributes[0]] ||
- '',
+ action.payload?.selectedItem?.[displayAttribute] || '',
};
+ }
case 'InputKeyDownArrowDown':
case 'InputKeyDownArrowUp': {
@@ -153,7 +157,7 @@ export const createReducer =
}
const inputValue = selectedItem
- ? selectedItem[searchAttributes[0]]
+ ? selectedItem[displayAttribute]
: '';
return {
...state,