forked from deephaven/web-client-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Basic ListView implementation (deephaven#1909)
- Loading branch information
Showing
11 changed files
with
384 additions
and
38 deletions.
There are no files selected for viewing
This file contains 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,119 @@ | ||
import React, { useCallback, useState } from 'react'; | ||
import { Grid, Item, ListView, ItemKey, Text } from '@deephaven/components'; | ||
import { vsAccount, vsPerson } from '@deephaven/icons'; | ||
import { Icon } from '@adobe/react-spectrum'; | ||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||
import { sampleSectionIdAndClasses } from './utils'; | ||
|
||
// Generate enough items to require scrolling | ||
const itemsSimple = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' | ||
.split('') | ||
.map((key, i) => ({ | ||
key, | ||
item: { key: (i + 1) * 100, content: `${key}${key}${key}` }, | ||
})); | ||
|
||
function AccountIcon({ | ||
slot, | ||
}: { | ||
slot?: 'illustration' | 'image'; | ||
}): JSX.Element { | ||
return ( | ||
// Images in ListView items require a slot of 'image' or 'illustration' to | ||
// be set in order to be positioned correctly: | ||
// https://github.com/adobe/react-spectrum/blob/784737effd44b9d5e2b1316e690da44555eafd7e/packages/%40react-spectrum/list/src/ListViewItem.tsx#L266-L267 | ||
<Icon slot={slot}> | ||
<FontAwesomeIcon icon={vsAccount} /> | ||
</Icon> | ||
); | ||
} | ||
|
||
export function ListViews(): JSX.Element { | ||
const [selectedKeys, setSelectedKeys] = useState<'all' | Iterable<ItemKey>>( | ||
[] | ||
); | ||
|
||
const onChange = useCallback((keys: 'all' | Iterable<ItemKey>): void => { | ||
setSelectedKeys(keys); | ||
}, []); | ||
|
||
return ( | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
<div {...sampleSectionIdAndClasses('list-views')}> | ||
<h2 className="ui-title">List View</h2> | ||
|
||
<Grid columnGap={14} height="size-4600"> | ||
<Text>Single Child</Text> | ||
<ListView | ||
gridRow="2" | ||
aria-label="Single Child" | ||
selectionMode="multiple" | ||
> | ||
<Item>Aaa</Item> | ||
</ListView> | ||
|
||
<label>Icons</label> | ||
<ListView gridRow="2" aria-label="Icon" selectionMode="multiple"> | ||
<Item textValue="Item with icon A"> | ||
<AccountIcon slot="image" /> | ||
<Text>Item with icon A</Text> | ||
</Item> | ||
<Item textValue="Item with icon B"> | ||
<AccountIcon slot="image" /> | ||
<Text>Item with icon B</Text> | ||
</Item> | ||
<Item textValue="Item with icon C"> | ||
<AccountIcon slot="image" /> | ||
<Text>Item with icon C</Text> | ||
</Item> | ||
<Item textValue="Item with icon D"> | ||
<AccountIcon slot="image" /> | ||
<Text>Item with icon D with overflowing content</Text> | ||
</Item> | ||
</ListView> | ||
|
||
<label>Mixed Children Types</label> | ||
<ListView | ||
gridRow="2" | ||
aria-label="Mixed Children Types" | ||
maxWidth="size-2400" | ||
selectionMode="multiple" | ||
defaultSelectedKeys={[999, 444]} | ||
> | ||
{/* 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"> | ||
<Icon slot="image"> | ||
<FontAwesomeIcon icon={vsPerson} /> | ||
</Icon> | ||
<Text>Complex Ccc with text that should be truncated</Text> | ||
</Item> | ||
</ListView> | ||
|
||
<label>Controlled</label> | ||
<ListView | ||
gridRow="2" | ||
aria-label="Controlled" | ||
selectionMode="multiple" | ||
selectedKeys={selectedKeys} | ||
onChange={onChange} | ||
> | ||
{itemsSimple} | ||
</ListView> | ||
</Grid> | ||
</div> | ||
); | ||
} | ||
|
||
export default ListViews; |
This file contains 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 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 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 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 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,103 @@ | ||
import { useMemo } from 'react'; | ||
import { | ||
ListView as SpectrumListView, | ||
SpectrumListViewProps, | ||
} from '@adobe/react-spectrum'; | ||
import cl from 'classnames'; | ||
import { | ||
ItemElementOrPrimitive, | ||
ItemKey, | ||
NormalizedItem, | ||
normalizeItemList, | ||
normalizeTooltipOptions, | ||
TooltipOptions, | ||
useRenderNormalizedItem, | ||
useStringifiedMultiSelection, | ||
} from '../utils'; | ||
|
||
export type ListViewProps = { | ||
children: | ||
| ItemElementOrPrimitive | ||
| ItemElementOrPrimitive[] | ||
| NormalizedItem[]; | ||
/** Can be set to true or a TooltipOptions to enable item tooltips */ | ||
tooltip?: boolean | TooltipOptions; | ||
selectedKeys?: 'all' | Iterable<ItemKey>; | ||
defaultSelectedKeys?: 'all' | Iterable<ItemKey>; | ||
disabledKeys?: Iterable<ItemKey>; | ||
/** | ||
* Handler that is called when the selection change. | ||
* Note that under the hood, this is just an alias for Spectrum's | ||
* `onSelectionChange`. We are renaming for better consistency with other | ||
* components. | ||
*/ | ||
onChange?: (keys: 'all' | Set<ItemKey>) => void; | ||
|
||
/** | ||
* Handler that is called when the selection changes. | ||
* @deprecated Use `onChange` instead | ||
*/ | ||
onSelectionChange?: (keys: 'all' | Set<ItemKey>) => void; | ||
} & Omit< | ||
SpectrumListViewProps<NormalizedItem>, | ||
| 'children' | ||
| 'items' | ||
| 'selectedKeys' | ||
| 'defaultSelectedKeys' | ||
| 'disabledKeys' | ||
| 'onSelectionChange' | ||
>; | ||
|
||
export function ListView({ | ||
children, | ||
tooltip = true, | ||
selectedKeys, | ||
defaultSelectedKeys, | ||
disabledKeys, | ||
UNSAFE_className, | ||
onChange, | ||
onSelectionChange, | ||
...spectrumListViewProps | ||
}: ListViewProps): JSX.Element { | ||
const normalizedItems = useMemo( | ||
() => normalizeItemList(children), | ||
[children] | ||
); | ||
|
||
const tooltipOptions = useMemo( | ||
() => normalizeTooltipOptions(tooltip, 'bottom'), | ||
[tooltip] | ||
); | ||
|
||
const renderNormalizedItem = useRenderNormalizedItem(tooltipOptions); | ||
|
||
const { | ||
selectedStringKeys, | ||
defaultSelectedStringKeys, | ||
disabledStringKeys, | ||
onStringSelectionChange, | ||
} = useStringifiedMultiSelection({ | ||
normalizedItems, | ||
selectedKeys, | ||
defaultSelectedKeys, | ||
disabledKeys, | ||
onChange: onChange ?? onSelectionChange, | ||
}); | ||
|
||
return ( | ||
<SpectrumListView | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...spectrumListViewProps} | ||
UNSAFE_className={cl('dh-list-view', UNSAFE_className)} | ||
items={normalizedItems} | ||
selectedKeys={selectedStringKeys} | ||
defaultSelectedKeys={defaultSelectedStringKeys} | ||
disabledKeys={disabledStringKeys} | ||
onSelectionChange={onStringSelectionChange} | ||
> | ||
{renderNormalizedItem} | ||
</SpectrumListView> | ||
); | ||
} | ||
|
||
export default ListView; |
This file contains 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 './ListView'; |
This file contains 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 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 |
---|---|---|
@@ -1,2 +1,4 @@ | ||
export * from './itemUtils'; | ||
export * from './themeUtils'; | ||
export * from './useRenderNormalizedItem'; | ||
export * from './useStringifiedMultiSelection'; |
39 changes: 39 additions & 0 deletions
39
packages/components/src/spectrum/utils/useRenderNormalizedItem.tsx
This file contains 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,39 @@ | ||
import { Key, useCallback } from 'react'; | ||
import { ItemContent } from '../ItemContent'; | ||
import { Item } from '../shared'; | ||
import { getItemKey, NormalizedItem, TooltipOptions } from './itemUtils'; | ||
|
||
export function useRenderNormalizedItem( | ||
tooltipOptions: TooltipOptions | null | ||
): (normalizedItem: NormalizedItem) => JSX.Element { | ||
return useCallback( | ||
(normalizedItem: NormalizedItem) => { | ||
const key = getItemKey(normalizedItem); | ||
const content = normalizedItem.item?.content ?? ''; | ||
const textValue = normalizedItem.item?.textValue ?? ''; | ||
|
||
return ( | ||
<Item | ||
// Note that setting the `key` prop explicitly on `Item` elements | ||
// causes the picker to expect `selectedKey` and `defaultSelectedKey` | ||
// to be strings. It also passes the stringified value of the key to | ||
// `onSelectionChange` handlers` regardless of the actual type of the | ||
// key. We can't really get around setting in order to support Windowed | ||
// data, so we'll need to do some manual conversion of keys to strings | ||
// in other places of this component. | ||
key={key as Key} | ||
// The `textValue` prop gets used to provide the content of `<option>` | ||
// elements that back the Spectrum Picker. These are not visible in the UI, | ||
// but are used for accessibility purposes, so we set to an arbitrary | ||
// 'Empty' value so that they are not empty strings. | ||
textValue={textValue === '' ? 'Empty' : textValue} | ||
> | ||
<ItemContent tooltipOptions={tooltipOptions}>{content}</ItemContent> | ||
</Item> | ||
); | ||
}, | ||
[tooltipOptions] | ||
); | ||
} | ||
|
||
export default useRenderNormalizedItem; |
Oops, something went wrong.