-
Notifications
You must be signed in to change notification settings - Fork 33
feat: Picker Component #1821
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
Merged
bmingles
merged 23 commits into
deephaven:main
from
bmingles:plugins-292-ui-picker-constant
Feb 27, 2024
Merged
feat: Picker Component #1821
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
5be7ef4
Rough first pass at Picker (#plugins-292)
bmingles 2c773f3
Support children and items (#plugins-292)
bmingles fb6dadd
Item tooltip support (#plugins-292)
bmingles 0ae647d
Split out helper component (#plugins-292)
bmingles c3aa11a
Tooltip permutations (#plugins-292)
bmingles 26b7920
Split out helper props types (#plugins-292)
bmingles a276587
textValue support (#plugins-292)
bmingles fcdf1e0
Removed custom Item component (#plugins-292)
bmingles 2231c8c
Cleanup (#plugins-292)
bmingles be8b64c
Cleanup (#plugins-292)
bmingles 7212b0f
PickerChildrenOrItemsProps type (#plugins-292)
bmingles ba961e7
Renamed utils + comments (#plugins-292)
bmingles 6526ee1
Moved Picker to subfolder (#plugins-292)
bmingles 9fdc88a
Picker tests (#plugins-292)
bmingles bea9de7
Split out PickerItemContent (#plugins-292)
bmingles df50476
e2e snapshots (#plugins-292)
bmingles 8e05713
Removed items prop (#plugins-292)
bmingles 8a5c2ae
Renamed variables (#plugins-292)
bmingles 3b8d49f
Added boolean support to picker item types (#plugins-292)
bmingles a1528e0
Added `onChange` handler (#plugins-292)
bmingles 641b160
Typo fix (#plugins-292)
bmingles 9316a00
Deprecated onSelectionChange (#plugins-292)
bmingles a8173a1
Added comments (#plugins-292)
bmingles File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import React from 'react'; | ||
| import { Picker } from '@deephaven/components'; | ||
| import { vsPerson } from '@deephaven/icons'; | ||
| import { Flex, Icon, Item, Text } from '@adobe/react-spectrum'; | ||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||
| import { sampleSectionIdAndClasses } from './utils'; | ||
|
|
||
| function PersonIcon(): JSX.Element { | ||
| return ( | ||
| <Icon> | ||
| <FontAwesomeIcon icon={vsPerson} /> | ||
| </Icon> | ||
| ); | ||
| } | ||
|
|
||
| export function Pickers(): JSX.Element { | ||
| return ( | ||
| // eslint-disable-next-line react/jsx-props-no-spreading | ||
| <div {...sampleSectionIdAndClasses('pickers')}> | ||
| <h2 className="ui-title">Pickers</h2> | ||
|
|
||
| <Flex gap={14}> | ||
| <Picker label="Single Child" tooltip={{ placement: 'bottom-end' }}> | ||
| <Item>Aaa</Item> | ||
| </Picker> | ||
|
|
||
| <Picker label="Mixed Children Types" tooltip> | ||
| {/* eslint-disable react/jsx-curly-brace-presence */} | ||
| {'String 1'} | ||
| {'String 2'} | ||
| {'String 3'} | ||
| {''} | ||
| {'Some really long text that should get truncated'} | ||
| {/* eslint-enable react/jsx-curly-brace-presence */} | ||
| {444} | ||
| {999} | ||
| {true} | ||
| {false} | ||
| <Item>Item Aaa</Item> | ||
| <Item>Item Bbb</Item> | ||
| <Item textValue="Complex Ccc"> | ||
| <PersonIcon /> | ||
| <Text>Complex Ccc</Text> | ||
| </Item> | ||
| </Picker> | ||
| </Flex> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default Pickers; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './picker'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import { useMemo } from 'react'; | ||
| import { Item, Picker as SpectrumPicker } from '@adobe/react-spectrum'; | ||
| import { Tooltip } from '../../popper'; | ||
| import { | ||
| NormalizedSpectrumPickerProps, | ||
| normalizePickerItemList, | ||
| normalizeTooltipOptions, | ||
| PickerItem, | ||
| PickerItemKey, | ||
| TooltipOptions, | ||
| } from './PickerUtils'; | ||
| import { PickerItemContent } from './PickerItemContent'; | ||
|
|
||
| export type PickerProps = { | ||
| children: PickerItem | PickerItem[]; | ||
| /** Can be set to true or a TooltipOptions to enable item tooltips */ | ||
| tooltip?: boolean | TooltipOptions; | ||
| /** The currently selected key in the collection (controlled). */ | ||
| selectedKey?: PickerItemKey | null; | ||
| /** The initial selected key in the collection (uncontrolled). */ | ||
| defaultSelectedKey?: PickerItemKey; | ||
| /** | ||
| * Handler that is called when the selection change. | ||
| * Note that this is just an alias for `onSelectionChange` for better | ||
| * consistency with other components. | ||
| */ | ||
| onChange?: (key: PickerItemKey) => void; | ||
| /** Handler that is called when the selection changes. */ | ||
| onSelectionChange?: (key: PickerItemKey) => void; | ||
| } /* | ||
| * Support remaining SpectrumPickerProps. | ||
| * Note that `selectedKey`, `defaultSelectedKey`, and `onSelectionChange` are | ||
| * re-defined above to account for boolean types which aren't included in the | ||
| * React `Key` type, but are actually supported by the Spectrum Picker component. | ||
| */ & Omit< | ||
| NormalizedSpectrumPickerProps, | ||
| | 'children' | ||
| | 'items' | ||
| | 'onSelectionChange' | ||
| | 'selectedKey' | ||
| | 'defaultSelectedKey' | ||
| >; | ||
|
|
||
| /** | ||
| * Picker component for selecting items from a list of items. Items can be | ||
| * provided via the `items` prop or as children. Each item can be a string, | ||
| * number, boolean, or a Spectrum <Item> element. The remaining props are just | ||
| * pass through props for the Spectrum Picker component. | ||
| * See https://react-spectrum.adobe.com/react-spectrum/Picker.html | ||
| */ | ||
| export function Picker({ | ||
| children, | ||
| tooltip, | ||
| defaultSelectedKey, | ||
| selectedKey, | ||
| onChange, | ||
| onSelectionChange, | ||
| ...spectrumPickerProps | ||
| }: PickerProps): JSX.Element { | ||
| const normalizedItems = useMemo( | ||
| () => normalizePickerItemList(children), | ||
| [children] | ||
| ); | ||
|
|
||
| const tooltipOptions = useMemo( | ||
| () => normalizeTooltipOptions(tooltip), | ||
| [tooltip] | ||
| ); | ||
|
|
||
| return ( | ||
| <SpectrumPicker | ||
| // eslint-disable-next-line react/jsx-props-no-spreading | ||
| {...spectrumPickerProps} | ||
| items={normalizedItems} | ||
| // Type assertions are necessary for `selectedKey`, `defaultSelectedKey`, | ||
| // and `onSelectionChange` due to Spectrum types not accounting for | ||
| // `boolean` keys | ||
| selectedKey={selectedKey as NormalizedSpectrumPickerProps['selectedKey']} | ||
| defaultSelectedKey={ | ||
| defaultSelectedKey as NormalizedSpectrumPickerProps['defaultSelectedKey'] | ||
| } | ||
| // `onChange` is just an alias for `onSelectionChange` | ||
| onSelectionChange={ | ||
| (onChange ?? | ||
| onSelectionChange) as NormalizedSpectrumPickerProps['onSelectionChange'] | ||
| } | ||
| > | ||
| {({ content, textValue }) => ( | ||
| <Item textValue={textValue === '' ? 'Empty' : textValue}> | ||
| <PickerItemContent>{content}</PickerItemContent> | ||
| {tooltipOptions == null || content === '' ? null : ( | ||
| <Tooltip options={tooltipOptions}>{content}</Tooltip> | ||
| )} | ||
| </Item> | ||
| )} | ||
| </SpectrumPicker> | ||
| ); | ||
| } | ||
|
|
||
| export default Picker; | ||
31 changes: 31 additions & 0 deletions
31
packages/components/src/spectrum/picker/PickerItemContent.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { isValidElement, ReactNode } from 'react'; | ||
| import { Text } from '@adobe/react-spectrum'; | ||
| import stylesCommon from '../../SpectrumComponent.module.scss'; | ||
|
|
||
| export interface PickerItemContentProps { | ||
| children: ReactNode; | ||
| } | ||
|
|
||
| /** | ||
| * Picker item content. Text content will be wrapped in a Spectrum Text | ||
| * component with ellipsis overflow handling. | ||
| */ | ||
| export function PickerItemContent({ | ||
| children: content, | ||
| }: PickerItemContentProps): JSX.Element { | ||
| if (isValidElement(content)) { | ||
| return content; | ||
| } | ||
|
|
||
| if (content === '') { | ||
| // Prevent the item height from collapsing when the content is empty | ||
| // eslint-disable-next-line no-param-reassign | ||
| content = <> </>; | ||
| } | ||
|
|
||
| return ( | ||
| <Text UNSAFE_className={stylesCommon.spectrumEllipsis}>{content}</Text> | ||
| ); | ||
| } | ||
|
|
||
| export default PickerItemContent; |
142 changes: 142 additions & 0 deletions
142
packages/components/src/spectrum/picker/PickerUtils.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| import React from 'react'; | ||
| import { Item, Text } from '@adobe/react-spectrum'; | ||
| import { | ||
| NormalizedPickerItem, | ||
| normalizeTooltipOptions, | ||
| normalizePickerItemList, | ||
| PickerItem, | ||
| } from './PickerUtils'; | ||
| import type { PickerProps } from './Picker'; | ||
|
|
||
| beforeEach(() => { | ||
| expect.hasAssertions(); | ||
| }); | ||
|
|
||
| /* eslint-disable react/jsx-key */ | ||
| const expectedNormalizations = new Map<PickerItem, NormalizedPickerItem>([ | ||
| [ | ||
| 999, | ||
| { | ||
| content: '999', | ||
| key: 999, | ||
| textValue: '999', | ||
| }, | ||
| ], | ||
| [ | ||
| true, | ||
| { | ||
| content: 'true', | ||
| key: true, | ||
| textValue: 'true', | ||
| }, | ||
| ], | ||
| [ | ||
| false, | ||
| { | ||
| content: 'false', | ||
| key: false, | ||
| textValue: 'false', | ||
| }, | ||
| ], | ||
| [ | ||
| '', | ||
| { | ||
| content: '', | ||
| key: '', | ||
| textValue: '', | ||
| }, | ||
| ], | ||
| [ | ||
| 'String', | ||
| { | ||
| content: 'String', | ||
| key: 'String', | ||
| textValue: 'String', | ||
| }, | ||
| ], | ||
| [ | ||
| <Item>Single string child no textValue</Item>, | ||
| { | ||
| content: 'Single string child no textValue', | ||
| key: 'Single string child no textValue', | ||
| textValue: 'Single string child no textValue', | ||
| }, | ||
| ], | ||
| [ | ||
| <Item> | ||
| <span>No textValue</span> | ||
| </Item>, | ||
| { | ||
| content: <span>No textValue</span>, | ||
| key: '', | ||
| textValue: '', | ||
| }, | ||
| ], | ||
| [ | ||
| <Item textValue="textValue">Single string</Item>, | ||
| { | ||
| content: 'Single string', | ||
| key: 'Single string', | ||
| textValue: 'textValue', | ||
| }, | ||
| ], | ||
| [ | ||
| <Item key="explicit.key" textValue="textValue"> | ||
| Explicit key | ||
| </Item>, | ||
| { | ||
| content: 'Explicit key', | ||
| key: 'explicit.key', | ||
| textValue: 'textValue', | ||
| }, | ||
| ], | ||
| [ | ||
| <Item textValue="textValue"> | ||
| <i>i</i> | ||
| <Text>Complex</Text> | ||
| </Item>, | ||
| { | ||
| content: [<i>i</i>, <Text>Complex</Text>], | ||
| key: 'textValue', | ||
| textValue: 'textValue', | ||
| }, | ||
| ], | ||
| ]); | ||
| /* eslint-enable react/jsx-key */ | ||
|
|
||
| const mixedItems = [...expectedNormalizations.keys()]; | ||
|
|
||
| const children = { | ||
| empty: [] as PickerProps['children'], | ||
| single: mixedItems[0] as PickerProps['children'], | ||
| mixed: mixedItems as PickerProps['children'], | ||
| }; | ||
|
|
||
| describe('normalizePickerItemList', () => { | ||
| it.each([children.empty, children.single, children.mixed])( | ||
| 'should return normalized picker items: %s', | ||
| given => { | ||
| const childrenArray = Array.isArray(given) ? given : [given]; | ||
|
|
||
| const expected = childrenArray.map(item => | ||
| expectedNormalizations.get(item) | ||
| ); | ||
|
|
||
| const actual = normalizePickerItemList(given); | ||
| expect(actual).toEqual(expected); | ||
| } | ||
| ); | ||
| }); | ||
|
|
||
| describe('normalizeTooltipOptions', () => { | ||
| it.each([ | ||
| [undefined, null], | ||
| [null, null], | ||
| [false, null], | ||
| [true, { placement: 'top-start' }], | ||
| [{ placement: 'bottom-end' }, { placement: 'bottom-end' }], | ||
| ] as const)('should return: %s', (options, expected) => { | ||
| const actual = normalizeTooltipOptions(options); | ||
| expect(actual).toEqual(expected); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Perhaps we should mark this as deprecated to encourage usage of
onChangeinstead?