Skip to content

Commit

Permalink
Started simplifying item normalization and rendering (deephaven#1890)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmingles committed Apr 19, 2024
1 parent 574a2c8 commit 363043e
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 66 deletions.
2 changes: 1 addition & 1 deletion packages/code-studio/src/styleguide/ListViews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function ListViews(): JSX.Element {
density="compact"
maxWidth="size-2400"
selectionMode="multiple"
defaultSelectedKeys={[999, 444]}
defaultSelectedKeys={['999', 444]}
>
{/* eslint-disable react/jsx-curly-brace-presence */}
{'String 1'}
Expand Down
97 changes: 39 additions & 58 deletions packages/components/src/spectrum/listView/ListView.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { useMemo } from 'react';
import cl from 'classnames';
import {
ListView as SpectrumListView,
SpectrumListViewProps,
} from '@adobe/react-spectrum';
import { SpectrumListViewProps } from '@adobe/react-spectrum';
import { EMPTY_FUNCTION } from '@deephaven/utils';
import {
extractSpectrumHTMLElement,
Expand All @@ -12,17 +9,16 @@ import {
} from '@deephaven/react-hooks';
import { Flex } from '../layout';
import {
isNormalizedItemsWithKeysList,
ItemElementOrPrimitive,
ItemKey,
ItemSelection,
NormalizedItem,
normalizeItemList,
normalizeTooltipOptions,
TooltipOptions,
useRenderItemFlags,
useRenderNormalizedItem,
useStringifiedMultiSelection,
} from '../utils';
import ListViewFromChildren from './ListViewFromChildren';
import ListViewFromItems from './ListViewFromItems';

export type ListViewProps = {
children:
Expand Down Expand Up @@ -75,53 +71,17 @@ export type ListViewProps = {
export function ListView({
children,
tooltip = true,
selectedKeys,
defaultSelectedKeys,
disabledKeys,
showItemIcons: showItemIconsDefault,
showItemDescriptions: showItemDescriptionsDefault,
showItemIcons = false,
showItemDescriptions = false,
UNSAFE_className,
onChange,
onScroll = EMPTY_FUNCTION,
onSelectionChange,
...spectrumListViewProps
}: ListViewProps): JSX.Element | null {
const normalizedItems = useMemo(
() => normalizeItemList(children),
[children]
);

const tooltipOptions = useMemo(
() => normalizeTooltipOptions(tooltip, 'bottom'),
[tooltip]
);

const { showItemIcons, showItemDescriptions } = useRenderItemFlags({
normalizedItems,
showItemIcons: showItemIconsDefault,
showItemDescriptions: showItemDescriptionsDefault,
});

const renderNormalizedItem = useRenderNormalizedItem({
itemIconSlot: 'image',
showItemDescriptions,
showItemIcons,
tooltipOptions,
});

const {
selectedStringKeys,
defaultSelectedStringKeys,
disabledStringKeys,
onStringSelectionChange,
} = useStringifiedMultiSelection({
normalizedItems,
selectedKeys,
defaultSelectedKeys,
disabledKeys,
onChange: onChange ?? onSelectionChange,
});

const scrollRef = useOnScrollRef(onScroll, extractSpectrumHTMLElement);

// Spectrum ListView crashes when it has zero height. Track the contentRect
Expand All @@ -131,6 +91,38 @@ export function ListView({
extractSpectrumHTMLElement
);

const listView = useMemo(
() =>
isNormalizedItemsWithKeysList(children) ? (
<ListViewFromItems
// eslint-disable-next-line react/jsx-props-no-spreading
{...spectrumListViewProps}
ref={scrollRef}
showItemDescriptions={showItemDescriptions}
showItemIcons={showItemIcons}
tooltipOptions={tooltipOptions}
items={children}
/>
) : (
<ListViewFromChildren
// eslint-disable-next-line react/jsx-props-no-spreading
{...spectrumListViewProps}
ref={scrollRef}
tooltipOptions={tooltipOptions}
>
{children}
</ListViewFromChildren>
),
[
children,
scrollRef,
showItemDescriptions,
showItemIcons,
spectrumListViewProps,
tooltipOptions,
]
);

return (
<Flex
ref={contentRectRef}
Expand All @@ -153,18 +145,7 @@ export function ListView({
// 4. ListView is rendered again.
<>&nbsp;</>
) : (
<SpectrumListView
// eslint-disable-next-line react/jsx-props-no-spreading
{...spectrumListViewProps}
ref={scrollRef}
items={normalizedItems}
selectedKeys={selectedStringKeys}
defaultSelectedKeys={defaultSelectedStringKeys}
disabledKeys={disabledStringKeys}
onSelectionChange={onStringSelectionChange}
>
{renderNormalizedItem}
</SpectrumListView>
listView
)}
</Flex>
);
Expand Down
62 changes: 62 additions & 0 deletions packages/components/src/spectrum/listView/ListViewFromChildren.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { forwardRef, useMemo } from 'react';
import {
ListView as SpectrumListView,
SpectrumListViewProps,
} from '@adobe/react-spectrum';
import type { DOMRefValue } from '@react-types/shared';
import type { ListViewPropsCommon } from './ListViewModel';
import {
ItemElementOrPrimitive,
ItemKey,
normalizeAsItemElementList,
TooltipOptions,
} from '../utils';

export interface ListViewFromChildrenProps extends ListViewPropsCommon {
tooltipOptions: TooltipOptions | null;
children: ItemElementOrPrimitive | ItemElementOrPrimitive[];
}

export const ListViewFromChildren = forwardRef<
DOMRefValue<HTMLDivElement>,
ListViewFromChildrenProps
>(
({
children,
selectedKeys,
defaultSelectedKeys,
disabledKeys,
tooltipOptions,
onChange,
onSelectionChange,
...props
}): JSX.Element => {
const wrappedChildren = useMemo(
() => normalizeAsItemElementList(children),
[children]
);

return (
<SpectrumListView
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
selectedKeys={
selectedKeys as SpectrumListViewProps<ItemKey>['selectedKeys']
}
defaultSelectedKeys={
defaultSelectedKeys as SpectrumListViewProps<ItemKey>['defaultSelectedKeys']
}
disabledKeys={
disabledKeys as SpectrumListViewProps<ItemKey>['disabledKeys']
}
onSelectionChange={onChange ?? onSelectionChange}
>
{wrappedChildren}
</SpectrumListView>
);
}
);

ListViewFromChildren.displayName = 'ListViewFromChildren';

export default ListViewFromChildren;
77 changes: 77 additions & 0 deletions packages/components/src/spectrum/listView/ListViewFromItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { forwardRef } from 'react';
import { ListView as SpectrumListView } from '@adobe/react-spectrum';
import type { DOMRefValue } from '@react-types/shared';
import {
NormalizedItem,
TooltipOptions,
useRenderNormalizedItem,
useStringifiedMultiSelection,
} from '../utils';
import { ListViewPropsCommon } from './ListViewModel';

export interface ListViewFromItemsProps extends ListViewPropsCommon {
items: NormalizedItem[];
showItemDescriptions: boolean;
showItemIcons: boolean;
tooltipOptions: TooltipOptions | null;
}

export const ListViewFromItems = forwardRef<
DOMRefValue<HTMLDivElement>,
ListViewFromItemsProps
>(
(
{
selectedKeys,
defaultSelectedKeys,
disabledKeys,
showItemDescriptions,
showItemIcons,
tooltipOptions,
items,
onChange,
onSelectionChange,
...props
},
forwardedRef
): JSX.Element => {
const renderNormalizedItem = useRenderNormalizedItem({
itemIconSlot: 'image',
showItemDescriptions,
showItemIcons,
tooltipOptions,
});

const {
selectedStringKeys,
defaultSelectedStringKeys,
disabledStringKeys,
onStringSelectionChange,
} = useStringifiedMultiSelection({
normalizedItems: items,
selectedKeys,
defaultSelectedKeys,
disabledKeys,
onChange: onChange ?? onSelectionChange,
});

return (
<SpectrumListView
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
ref={forwardedRef}
items={items}
selectedKeys={selectedStringKeys}
defaultSelectedKeys={defaultSelectedStringKeys}
disabledKeys={disabledStringKeys}
onSelectionChange={onStringSelectionChange}
>
{renderNormalizedItem}
</SpectrumListView>
);
}
);

ListViewFromItems.displayName = 'ListViewFromItems';

export default ListViewFromItems;
30 changes: 30 additions & 0 deletions packages/components/src/spectrum/listView/ListViewModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { SpectrumListViewProps } from '@adobe/react-spectrum';
import type { ItemKey, ItemSelection, NormalizedItem } from '../utils';

export type ListViewPropsCommon = {
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: ItemSelection) => void;

/**
* Handler that is called when the selection changes.
* @deprecated Use `onChange` instead
*/
onSelectionChange?: (keys: ItemSelection) => void;
} & Omit<
SpectrumListViewProps<NormalizedItem>,
| 'children'
| 'items'
| 'selectedKeys'
| 'defaultSelectedKeys'
| 'disabledKeys'
| 'onSelectionChange'
>;
10 changes: 6 additions & 4 deletions packages/components/src/spectrum/utils/itemUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type SectionPropsNoItemRenderer<T> = Omit<SectionProps<T>, 'children'> & {
children: Exclude<SectionProps<T>['children'], ItemRenderer<T>>;
};

type ItemElement = ReactElement<ItemProps<unknown>>;
export type ItemElement = ReactElement<ItemProps<unknown>>;
export type SectionElement = ReactElement<SectionPropsNoItemRenderer<unknown>>;

export type ItemElementOrPrimitive = number | string | boolean | ItemElement;
Expand Down Expand Up @@ -219,9 +219,11 @@ function normalizeItemContent(item: ItemElement): {
* @param itemOrSection The item or section
* @returns A `ItemKey` for the item or undefined if a key can't be determined
*/
function normalizeItemKey(item: ItemElementOrPrimitive): ItemKey | undefined;
function normalizeItemKey(section: SectionElement): Key | undefined;
function normalizeItemKey(
export function normalizeItemKey(
item: ItemElementOrPrimitive
): ItemKey | undefined;
export function normalizeItemKey(section: SectionElement): Key | undefined;
export function normalizeItemKey(
itemOrSection: ItemElementOrPrimitive | SectionElement
): Key | ItemKey | undefined {
// string, number, or boolean
Expand Down
Loading

0 comments on commit 363043e

Please sign in to comment.