Skip to content

Commit 982d7be

Browse files
Elena RashkovanLena Rashkovan
andauthored
feat(select): add value customisation (#492)
Co-authored-by: Lena Rashkovan <[email protected]>
1 parent 8bc3397 commit 982d7be

File tree

2 files changed

+48
-34
lines changed

2 files changed

+48
-34
lines changed

src/components/experimental/Select/Select.tsx

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import React, { ReactElement, useState } from 'react';
1+
import React from 'react';
22
import {
33
Select as BaseSelect,
44
SelectProps as BaseSelectProps,
55
SelectValue,
66
SelectStateContext,
7-
FieldError
7+
FieldError,
8+
SelectValueRenderProps
89
} from 'react-aria-components';
910
import { useIsSSR } from 'react-aria';
1011
import { useResizeObserver } from '@react-aria/utils';
@@ -41,42 +42,47 @@ const FakeButton = styled(FakeInput)`
4142
}
4243
`;
4344

44-
interface SelectFieldProps extends Pick<FieldProps, 'label' | 'description' | 'errorMessage' | 'leadingIcon'> {
45+
interface SelectFieldProps<T> extends Pick<FieldProps, 'label' | 'description' | 'errorMessage' | 'leadingIcon'> {
4546
placeholder?: string;
47+
renderValue?: (props: SelectValueRenderProps<T> & { defaultChildren: React.ReactNode }) => React.ReactNode;
4648
}
4749

48-
const SelectTrigger = React.forwardRef<HTMLDivElement, SelectFieldProps>(
49-
({ label, leadingIcon, placeholder }, forwardedRef) => {
50-
const state = React.useContext(SelectStateContext);
51-
const buttonRef = React.useRef<HTMLButtonElement>(null);
50+
// eslint-disable-next-line @typescript-eslint/ban-types
51+
function SelectTriggerWithRef<T extends object>(
52+
{ label, leadingIcon, placeholder, renderValue }: SelectFieldProps<T>,
53+
forwardedRef: React.ForwardedRef<HTMLDivElement>
54+
) {
55+
const state = React.useContext(SelectStateContext);
56+
const buttonRef = React.useRef<HTMLButtonElement>(null);
5257

53-
return (
54-
<FakeButton
55-
$isVisuallyFocused={state?.isOpen}
56-
ref={forwardedRef}
57-
onClick={() => buttonRef.current?.click()}
58-
>
59-
{leadingIcon}
60-
<InnerWrapper>
61-
<Label $flying={Boolean(placeholder || state?.selectedItem)}>{label}</Label>
62-
<Button ref={buttonRef}>
63-
<SelectValue>
64-
{({ defaultChildren, isPlaceholder }) =>
65-
isPlaceholder
66-
? placeholder || <VisuallyHidden>{defaultChildren}</VisuallyHidden>
67-
: defaultChildren
68-
}
69-
</SelectValue>
70-
</Button>
71-
</InnerWrapper>
72-
{state?.isOpen ? <DropupSelectIcon /> : <DropdownSelectIcon />}
73-
</FakeButton>
74-
);
75-
}
76-
);
58+
return (
59+
<FakeButton $isVisuallyFocused={state?.isOpen} ref={forwardedRef} onClick={() => buttonRef.current?.click()}>
60+
{leadingIcon}
61+
<InnerWrapper>
62+
<Label $flying={Boolean(placeholder || state?.selectedItem)}>{label}</Label>
63+
<Button ref={buttonRef}>
64+
<SelectValue<T>>
65+
{selectValueRenderProps =>
66+
renderValue
67+
? renderValue(selectValueRenderProps)
68+
: (function defaultRenderValue({ isPlaceholder, defaultChildren }) {
69+
return isPlaceholder
70+
? placeholder || <VisuallyHidden>{defaultChildren}</VisuallyHidden>
71+
: defaultChildren;
72+
})(selectValueRenderProps)
73+
}
74+
</SelectValue>
75+
</Button>
76+
</InnerWrapper>
77+
{state?.isOpen ? <DropupSelectIcon /> : <DropdownSelectIcon />}
78+
</FakeButton>
79+
);
80+
}
81+
82+
const SelectTrigger = React.forwardRef(SelectTriggerWithRef);
7783

7884
interface SelectProps<T extends Record<string, unknown>>
79-
extends SelectFieldProps,
85+
extends SelectFieldProps<T>,
8086
Omit<BaseSelectProps<T>, 'children'> {
8187
items?: Iterable<T>;
8288
children: React.ReactNode | ((item: T) => React.ReactNode);
@@ -89,9 +95,10 @@ function Select<T extends Record<string, unknown>>({
8995
errorMessage,
9096
description,
9197
placeholder,
98+
renderValue,
9299
...props
93-
}: SelectProps<T>): ReactElement {
94-
const [menuWidth, setMenuWidth] = useState<string | null>(null);
100+
}: SelectProps<T>): React.ReactElement {
101+
const [menuWidth, setMenuWidth] = React.useState<string | null>(null);
95102
const triggerRef = React.useRef<HTMLDivElement>(null);
96103
const isSSR = useIsSSR();
97104

@@ -117,6 +124,7 @@ function Select<T extends Record<string, unknown>>({
117124
label={label}
118125
leadingIcon={leadingIcon}
119126
placeholder={placeholder}
127+
renderValue={renderValue}
120128
/>
121129
<Footer>{isInvalid ? <FieldError>{errorMessage}</FieldError> : description}</Footer>
122130
</Wrapper>

src/components/experimental/Select/docs/Select.stories.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,9 @@ export const Invalid: Story = {
7070
errorMessage: 'Error'
7171
}
7272
};
73+
74+
export const WithCustomValueRenderer: Story = {
75+
args: {
76+
renderValue: ({ selectedText, isPlaceholder }) => (isPlaceholder ? '' : `${selectedText} ♥`)
77+
}
78+
};

0 commit comments

Comments
 (0)