-
Notifications
You must be signed in to change notification settings - Fork 1.4k
wip: allow custom react element for S2 Picker value #9541
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7087906
6425a6f
eabc625
d5c4f3c
195d5d9
8324d81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,7 +123,13 @@ export interface PickerProps<T extends object, M extends SelectionMode = 'single | |
| /** Width of the menu. By default, matches width of the trigger. Note that the minimum width of the dropdown is always equal to the trigger's width. */ | ||
| menuWidth?: number, | ||
| /** The current loading state of the Picker. */ | ||
| loadingState?: LoadingState | ||
| loadingState?: LoadingState, | ||
| /** | ||
| * Custom renderer for the picker value. Allows one to provide a custom element to render selected items. | ||
| * | ||
| * @note The returned ReactNode should not have interactable elements as it will break accessibility. | ||
| */ | ||
| renderValue?: (selectedItems: T[]) => ReactNode | ||
| } | ||
|
|
||
| interface PickerButtonProps extends PickerStyleProps, ButtonRenderProps {} | ||
|
|
@@ -227,7 +233,8 @@ const valueStyles = style({ | |
| }, | ||
| truncate: true, | ||
| display: 'flex', | ||
| alignItems: 'center' | ||
| alignItems: 'center', | ||
| height: '100%' | ||
| }); | ||
|
|
||
| const iconStyles = style({ | ||
|
|
@@ -298,6 +305,7 @@ export const Picker = /*#__PURE__*/ (forwardRef as forwardRefType)(function Pick | |
| placeholder = stringFormatter.format('picker.placeholder'), | ||
| isQuiet, | ||
| loadingState, | ||
| renderValue, | ||
| onLoadMore, | ||
| ...pickerProps | ||
| } = props; | ||
|
|
@@ -377,6 +385,7 @@ export const Picker = /*#__PURE__*/ (forwardRef as forwardRefType)(function Pick | |
| </FieldLabel> | ||
| <PickerButton | ||
| loadingState={loadingState} | ||
| renderValue={renderValue} | ||
| isOpen={isOpen} | ||
| isQuiet={isQuiet} | ||
| isFocusVisible={isFocusVisible} | ||
|
|
@@ -483,7 +492,7 @@ const avatarSize = { | |
| XL: 26 | ||
| } as const; | ||
|
|
||
| interface PickerButtonInnerProps<T extends object> extends PickerStyleProps, Omit<AriaSelectRenderProps, 'isRequired' | 'isFocused'>, Pick<PickerProps<T>, 'loadingState'> { | ||
| interface PickerButtonInnerProps<T extends object> extends PickerStyleProps, Omit<AriaSelectRenderProps, 'isRequired' | 'isFocused'>, Pick<PickerProps<T>, 'loadingState' | 'renderValue'> { | ||
| loadingCircle: ReactNode, | ||
| buttonRef: RefObject<HTMLButtonElement | null> | ||
| } | ||
|
|
@@ -499,7 +508,8 @@ const PickerButton = createHideableComponent(function PickerButton<T extends obj | |
| isDisabled, | ||
| loadingState, | ||
| loadingCircle, | ||
| buttonRef | ||
| buttonRef, | ||
| renderValue | ||
| } = props; | ||
| let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2'); | ||
|
|
||
|
|
@@ -531,8 +541,20 @@ const PickerButton = createHideableComponent(function PickerButton<T extends obj | |
| })}> | ||
| {(renderProps) => ( | ||
| <> | ||
| <SelectValue className={valueStyles({isQuiet}) + ' ' + raw('&> :not([slot=icon], [slot=avatar], [slot=label], [data-slot=label]) {display: none;}')}> | ||
| <SelectValue | ||
| className={ | ||
| valueStyles({isQuiet}) + | ||
| (renderValue ? '' : ' ' + raw('&> :not([slot=icon], [slot=avatar], [slot=label], [data-slot=label]) {display: none;}')) | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was quite restrictive in allowing us our own layouts. This should allow custom layouts only through
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this makes sense. |
||
| }> | ||
| {({selectedItems, defaultChildren}) => { | ||
| const selectedValues = selectedItems.filter((item): item is T => item != null); | ||
| const defaultRenderedValue = selectedValues.length <= 1 | ||
| ? defaultChildren | ||
| : <Text slot="label">{stringFormatter.format('picker.selectedCount', {count: selectedValues.length})}</Text>; | ||
| const renderedValue = selectedValues.length > 0 && renderValue | ||
| ? renderValue(selectedValues) | ||
| : defaultRenderedValue; | ||
|
|
||
| return ( | ||
| <Provider | ||
| values={[ | ||
|
|
@@ -577,10 +599,7 @@ const PickerButton = createHideableComponent(function PickerButton<T extends obj | |
| }], | ||
| [InsideSelectValueContext, true] | ||
| ]}> | ||
| {selectedItems.length <= 1 | ||
| ? defaultChildren | ||
| : <Text slot="label">{stringFormatter.format('picker.selectedCount', {count: selectedItems.length})}</Text> | ||
| } | ||
| {renderedValue} | ||
| </Provider> | ||
| ); | ||
| }} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -242,6 +242,43 @@ function Example(props) { | |
| } | ||
| ``` | ||
|
|
||
| ### Custom Render Value | ||
|
|
||
| Use the `renderValue` prop to provide a custom element to display selected items. The callback is given an array of the selected user-defined objects. | ||
|
|
||
| ```tsx render docs={docs.exports.Picker} links={docs.links} props={['selectionMode']} wide | ||
| "use client"; | ||
| import {Picker, PickerItem} from '@react-spectrum/s2'; | ||
| import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; | ||
| import {useState} from 'react'; | ||
|
|
||
| function Example(props) { | ||
| let [animal, setAnimal] = useState("bison"); | ||
|
|
||
| return ( | ||
| <div> | ||
| <Picker | ||
| {...props} | ||
| label="Pick an animal" | ||
| ///- begin highlight -/// | ||
| /* PROPS */ | ||
| value={animal} | ||
| onChange={setAnimal} | ||
| ///- end highlight -/// | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like this example is missing |
||
| > | ||
| <PickerItem id="koala">Koala</PickerItem> | ||
| <PickerItem id="kangaroo">Kangaroo</PickerItem> | ||
| <PickerItem id="platypus" isDisabled>Platypus</PickerItem> | ||
| <PickerItem id="eagle">Bald Eagle</PickerItem> | ||
| <PickerItem id="bison">Bison</PickerItem> | ||
| <PickerItem id="skunk">Skunk</PickerItem> | ||
| </Picker> | ||
| <pre className={style({font: 'body'})}>Current selection: {JSON.stringify(animal)}</pre> | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ## Forms | ||
|
|
||
| Use the `name` prop to submit the `id` of the selected item to the server. Set the `isRequired` prop to validate that the user selects an option, or implement custom client or server-side validation. See the [Forms](forms) guide to learn more. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Locked height to picker button height. Enough for our use case as we don't have anything larger than the button that should cause it to resize.