Skip to content

Commit c1baf20

Browse files
feat(ffe-account-selector-react): add displayAttribute prop
makes it possible to override what is displayed in the AccountSelector input. So you can have a new custom attribute in the input, example a new attribute that contains: account_name - account_number previous default was the first attribute in the searchAttribute prop. The same should be the case with this change. Needed to add the displayAttribute to the searchAttributes passed to the SearchableDropdown, so the user don't see a message about 0 hits when changing the input.
1 parent e2bf820 commit c1baf20

File tree

4 files changed

+126
-1
lines changed

4 files changed

+126
-1
lines changed

packages/ffe-account-selector-react/src/account-selector/AccountSelector.mdx

+4
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ Dersom du ønsker å skjule kontodetaljer, kan du bruke `hideAccountDetails`.
3535
Dersom du ønsker å ha ekstra tekst på bunnen av lista, kan du bruke `postListElement`.
3636

3737
<Canvas of={AccountSelectorStories.PostListElement} />
38+
39+
Dersom du ønsker å vise en annen informasjon enn kontonavn i inputen, så kan du velge hvilken attribute som brukes med `displayAttribute`.
40+
41+
<Canvas of={AccountSelectorStories.CustomDisplayAttribute} />

packages/ffe-account-selector-react/src/account-selector/AccountSelector.spec.tsx

+66
Original file line numberDiff line numberDiff line change
@@ -618,4 +618,70 @@ describe('<AccountSelector/>', () => {
618618
expect(a11yStatusMessage).toHaveTextContent('');
619619
jest.useRealTimers();
620620
});
621+
622+
it('allows passing a custom display attribute', async () => {
623+
const handleAccountSelectedMock = jest.fn();
624+
625+
render(
626+
<AccountSelector
627+
id="id"
628+
labelledById="labelId"
629+
accounts={accounts}
630+
displayAttribute={'accountNumber'}
631+
locale="nb"
632+
onAccountSelected={handleAccountSelectedMock}
633+
onReset={onReset}
634+
selectedAccount={selectedAccount}
635+
ariaInvalid={false}
636+
/>,
637+
);
638+
639+
const input = screen.getByRole('combobox');
640+
641+
expect(input.getAttribute('value')).toBe('');
642+
fireEvent.change(input, { target: { value: 'Gr' } });
643+
644+
fireEvent.click(screen.getByText('Gris'));
645+
646+
expect(handleAccountSelectedMock).toHaveBeenCalledTimes(1);
647+
expect(handleAccountSelectedMock).toHaveBeenCalledWith(accounts[3]);
648+
expect(input.getAttribute('value')).toEqual('1253 47 789102');
649+
});
650+
651+
it('passing displayAttribute should make it searchable', async () => {
652+
type FunkyAccounts = Account & { funkySmell: string };
653+
const funkyAccounts: FunkyAccounts[] = accounts.map((account, idx) => ({
654+
...account,
655+
funkySmell: `Smells like money${idx}`,
656+
}));
657+
658+
render(
659+
<AccountSelector<FunkyAccounts>
660+
id="id"
661+
labelledById="labelId"
662+
accounts={funkyAccounts}
663+
displayAttribute={'funkySmell'}
664+
locale="nb"
665+
onAccountSelected={handleAccountSelected}
666+
onReset={onReset}
667+
selectedAccount={funkyAccounts[0]}
668+
ariaInvalid={false}
669+
/>,
670+
);
671+
672+
const input = screen.getByRole('combobox');
673+
fireEvent.click(input);
674+
675+
expect(screen.queryByText('Ingen samsvarende konto')).toBeNull();
676+
fireEvent.change(input, {
677+
target: { value: 'Dette skal få ingen match' },
678+
});
679+
expect(
680+
screen.queryByText('Ingen samsvarende konto'),
681+
).toBeInTheDocument();
682+
683+
fireEvent.change(input, { target: { value: 'money3' } });
684+
fireEvent.click(screen.getByText('Gris'));
685+
expect(input.getAttribute('value')).toEqual('Smells like money3');
686+
});
621687
});

packages/ffe-account-selector-react/src/account-selector/AccountSelector.stories.tsx

+39
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { AccountSelector } from './AccountSelector';
33
import { InputGroup } from '@sb1/ffe-form-react';
44
import type { StoryObj, Meta } from '@storybook/react';
55
import { SmallText } from '@sb1/ffe-core-react';
6+
import { accountFormatter } from '../format';
67

78
const meta: Meta<typeof AccountSelector> = {
89
title: 'Komponenter/Account-selector/AccountSelector',
@@ -254,3 +255,41 @@ export const InitialValue: Story = {
254255
);
255256
},
256257
};
258+
259+
type PrettyAccount = Account & { prettyName: string };
260+
261+
const prettyAccounts: PrettyAccount[] = accounts.map(account => ({
262+
...account,
263+
prettyName: `${account.name} - ${accountFormatter(account.accountNumber)}`,
264+
}));
265+
export const CustomDisplayAttribute: StoryObj<
266+
typeof AccountSelector<PrettyAccount>
267+
> = {
268+
args: {
269+
id: 'input-id',
270+
labelledById: 'label-id',
271+
locale: 'nb',
272+
formatAccountNumber: true,
273+
allowCustomAccount: false,
274+
displayAttribute: 'prettyName',
275+
accounts: prettyAccounts,
276+
},
277+
render: function Render(args) {
278+
const [selectedAccount, setSelectedAccount] = useState<PrettyAccount>(
279+
prettyAccounts[2],
280+
);
281+
return (
282+
<InputGroup
283+
label="Velg konto"
284+
inputId={args.id}
285+
labelId={args.labelledById}
286+
>
287+
<AccountSelector<PrettyAccount>
288+
{...args}
289+
selectedAccount={selectedAccount}
290+
onAccountSelected={setSelectedAccount}
291+
/>
292+
</InputGroup>
293+
);
294+
},
295+
};

packages/ffe-account-selector-react/src/account-selector/AccountSelector.tsx

+17-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export interface AccountSelectorProps<T extends Account = Account> {
4040
formatAccountNumber?: boolean;
4141
/** id of element that labels input field */
4242
labelledById?: string;
43+
/** Attribute used in the input when an item is selected. **/
44+
displayAttribute?: keyof T;
4345
/**
4446
* Allows selecting the text the user writes even if it does not match anything in the accounts array.
4547
* Useful e.g. if you want to pay to account that is not in yur recipients list.
@@ -91,6 +93,7 @@ export const AccountSelector = <T extends Account = Account>({
9193
ariaInvalid,
9294
onOpen,
9395
onClose,
96+
displayAttribute,
9497
...rest
9598
}: AccountSelectorProps<T>) => {
9699
const [inputValue, setInputValue] = useState(selectedAccount?.name || '');
@@ -119,6 +122,7 @@ export const AccountSelector = <T extends Account = Account>({
119122
onAccountSelected({
120123
name: value.name,
121124
accountNumber: value.name,
125+
...(displayAttribute ? { [displayAttribute]: value.name } : {}),
122126
} as T);
123127
setInputValue(value.name);
124128
} else {
@@ -137,6 +141,7 @@ export const AccountSelector = <T extends Account = Account>({
137141
<SearchableDropdown<T>
138142
id={id}
139143
labelledById={labelledById}
144+
displayAttribute={displayAttribute}
140145
inputProps={{
141146
...inputProps,
142147
onChange: onInputChange,
@@ -165,14 +170,25 @@ export const AccountSelector = <T extends Account = Account>({
165170
? formatter(inputValue)
166171
: inputValue,
167172
accountNumber: '',
173+
...(displayAttribute
174+
? {
175+
[displayAttribute]: formatter
176+
? formatter(inputValue)
177+
: inputValue,
178+
}
179+
: {}),
168180
} as T,
169181
],
170182
}
171183
: (noMatches ?? { text: texts[locale].noMatch })
172184
}
173185
formatter={formatter}
174186
onChange={handleAccountSelected}
175-
searchAttributes={['name', 'accountNumber']}
187+
searchAttributes={[
188+
'name',
189+
'accountNumber',
190+
...(displayAttribute ? [displayAttribute] : []),
191+
]}
176192
locale={locale}
177193
optionBody={({ item, isHighlighted, ...restOptionBody }) => {
178194
if (OptionBody) {

0 commit comments

Comments
 (0)