Skip to content

Commit

Permalink
Split out usePickerNormalizedProps hook (deephaven#2071)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmingles committed Jun 12, 2024
1 parent 805490f commit b50841d
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 94 deletions.
98 changes: 7 additions & 91 deletions packages/components/src/spectrum/picker/PickerNormalized.tsx
Original file line number Diff line number Diff line change
@@ -1,118 +1,34 @@
import { useMemo } from 'react';
import { Picker as SpectrumPicker } from '@adobe/react-spectrum';
import type { DOMRef } from '@react-types/shared';
import cl from 'classnames';
import { EMPTY_FUNCTION } from '@deephaven/utils';
import { Section } from '../shared';
import type { PickerNormalizedProps } from './PickerProps';

import {
getItemKey,
isNormalizedSection,
normalizeTooltipOptions,
useRenderNormalizedItem,
useStringifiedSelection,
} from '../utils';
import usePickerScrollOnOpen from './usePickerScrollOnOpen';
import usePickerNormalizedProps from './usePickerNormalizedProps';

/**
* Picker that takes an array of `NormalizedItem` or `NormalizedSection` items
* as children and uses a render item function to render the items. This is
* necessary to support windowed data.
*/
export function PickerNormalized({
normalizedItems,
tooltip = true,
selectedKey,
defaultSelectedKey,
disabledKeys,
showItemIcons,
UNSAFE_className,
getInitialScrollPosition,
onChange,
onOpenChange,
onScroll = EMPTY_FUNCTION,
onSelectionChange,
...props
}: PickerNormalizedProps): JSX.Element {
const tooltipOptions = useMemo(
() => normalizeTooltipOptions(tooltip),
[tooltip]
);

const renderNormalizedItem = useRenderNormalizedItem({
itemIconSlot: 'icon',
// Descriptions introduce variable item heights which throws off calculation
// of initial scroll position and setting viewport on windowed data. For now
// not going to implement description support in Picker.
// https://github.com/deephaven/web-client-ui/issues/1958
showItemDescriptions: false,
showItemIcons,
tooltipOptions,
});

// Spectrum doesn't re-render if only the `renderNormalizedItems` function
// changes, so we create a key from its dependencies that can be used to force
// re-render.
const forceRerenderKey = `${showItemIcons}-${tooltipOptions?.placement}`;

const { ref: scrollRef, onOpenChange: onOpenChangeInternal } =
usePickerScrollOnOpen({
getInitialScrollPosition,
onScroll,
onOpenChange,
});

// Spectrum Picker treats keys as strings if the `key` prop is explicitly
// set on `Item` elements. Since we do this in `renderItem`, we need to
// map original key types to and from strings so that selection works.
const {
selectedStringKey,
defaultSelectedStringKey,
disabledStringKeys,
onStringSelectionChange,
} = useStringifiedSelection({
normalizedItems,
selectedKey,
defaultSelectedKey,
disabledKeys,
onChange: onChange ?? onSelectionChange,
});
const { forceRerenderKey, ...pickerProps } = usePickerNormalizedProps<
PickerNormalizedProps,
HTMLDivElement
>(props);

return (
<SpectrumPicker
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
{...pickerProps}
key={forceRerenderKey}
ref={scrollRef as DOMRef<HTMLDivElement>}
UNSAFE_className={cl(
'dh-picker',
'dh-picker-normalized',
UNSAFE_className
)}
items={normalizedItems}
selectedKey={selectedStringKey}
defaultSelectedKey={defaultSelectedStringKey}
disabledKeys={disabledStringKeys}
onSelectionChange={onStringSelectionChange}
onOpenChange={onOpenChangeInternal}
>
{itemOrSection => {
if (isNormalizedSection(itemOrSection)) {
return (
<Section
key={getItemKey(itemOrSection)}
title={itemOrSection.item?.title}
items={itemOrSection.item?.items}
>
{renderNormalizedItem}
</Section>
);
}

return renderNormalizedItem(itemOrSection);
}}
</SpectrumPicker>
/>
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/components/src/spectrum/picker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export * from './Picker';
export * from './PickerNormalized';
export * from './PickerProps';
export * from './usePickerItemScale';
export * from './usePickerNormalizedProps';
export * from './usePickerProps';
export * from './usePickerScrollOnOpen';
144 changes: 144 additions & 0 deletions packages/components/src/spectrum/picker/usePickerNormalizedProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Key, useCallback, useMemo } from 'react';
import { EMPTY_FUNCTION } from '@deephaven/utils';
import type { DOMRef } from '@react-types/shared';
import {
getItemKey,
isNormalizedSection,
NormalizedItem,
NormalizedSection,
normalizeTooltipOptions,
useRenderNormalizedItem,
useStringifiedSelection,
} from '../utils';
import { PickerNormalizedPropsT } from './PickerProps';
import { usePickerScrollOnOpen } from './usePickerScrollOnOpen';
import { Section } from '../shared';

export type UsePickerNormalizedDerivedProps<THtml extends HTMLElement> = {
children: (itemOrSection: NormalizedItem | NormalizedSection) => JSX.Element;
forceRerenderKey: Key;
items: (NormalizedItem | NormalizedSection)[];
defaultSelectedKey?: Key;
disabledKeys?: Iterable<Key>;
ref: DOMRef<THtml>;
selectedKey?: Key | null;
onSelectionChange: (key: Key | null) => void;
onOpenChange: (isOpen: boolean) => void;
};

export type UsePickerNormalizedPassthroughProps<TProps> = Omit<
PickerNormalizedPropsT<TProps>,
| 'normalizedItems'
| 'tooltip'
| 'selectedKey'
| 'defaultSelectedKey'
| 'disabledKeys'
| 'showItemIcons'
| 'getInitialScrollPosition'
| 'onChange'
| 'onOpenChange'
| 'onScroll'
| 'onSelectionChange'
>;

export type UsePickerNormalizedProps<
TProps,
THtml extends HTMLElement,
> = UsePickerNormalizedDerivedProps<THtml> &
UsePickerNormalizedPassthroughProps<TProps>;

export function usePickerNormalizedProps<
TProps,
THtml extends HTMLElement = HTMLElement,
>({
normalizedItems,
tooltip = true,
selectedKey,
defaultSelectedKey,
disabledKeys,
showItemIcons,
getInitialScrollPosition,
onChange,
onOpenChange,
onScroll = EMPTY_FUNCTION,
onSelectionChange,
...props
}: PickerNormalizedPropsT<TProps>): UsePickerNormalizedProps<TProps, THtml> {
const tooltipOptions = useMemo(
() => normalizeTooltipOptions(tooltip),
[tooltip]
);

const renderNormalizedItem = useRenderNormalizedItem({
itemIconSlot: 'icon',
// Descriptions introduce variable item heights which throws off calculation
// of initial scroll position and setting viewport on windowed data. For now
// not going to implement description support in Picker.
// https://github.com/deephaven/web-client-ui/issues/1958
showItemDescriptions: false,
showItemIcons,
tooltipOptions,
});

// Spectrum doesn't re-render if only the `renderNormalizedItems` function
// changes, so we create a key from its dependencies that can be used to force
// re-render.
const forceRerenderKey = `${showItemIcons}-${tooltipOptions?.placement}`;

const { ref, onOpenChange: onOpenChangeInternal } =
usePickerScrollOnOpen<THtml>({
getInitialScrollPosition,
onScroll,
onOpenChange,
});

// Spectrum Picker treats keys as strings if the `key` prop is explicitly
// set on `Item` elements. Since we do this in `renderItem`, we need to
// map original key types to and from strings so that selection works.
const {
selectedStringKey,
defaultSelectedStringKey,
disabledStringKeys,
onStringSelectionChange,
} = useStringifiedSelection({
normalizedItems,
selectedKey,
defaultSelectedKey,
disabledKeys,
onChange: onChange ?? onSelectionChange,
});

const children = useCallback(
(itemOrSection: NormalizedItem | NormalizedSection) => {
if (isNormalizedSection(itemOrSection)) {
return (
<Section
key={getItemKey(itemOrSection)}
title={itemOrSection.item?.title}
items={itemOrSection.item?.items}
>
{renderNormalizedItem}
</Section>
);
}

return renderNormalizedItem(itemOrSection);
},
[renderNormalizedItem]
);

return {
...props,
children,
forceRerenderKey,
ref,
items: normalizedItems,
selectedKey: selectedStringKey,
defaultSelectedKey: defaultSelectedStringKey,
disabledKeys: disabledStringKeys,
onSelectionChange: onStringSelectionChange,
onOpenChange: onOpenChangeInternal,
};
}

export default usePickerNormalizedProps;
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ export interface UseStringifiedSelectionOptions {
selectedKey: ItemKey | null | undefined;
defaultSelectedKey: ItemKey | undefined;
disabledKeys: Iterable<ItemKey> | undefined;
onChange: ((key: ItemKey) => void) | undefined;
onChange: ((key: ItemKey | null) => void) | undefined;
}

export interface UseStringifiedSelectionResult {
defaultSelectedStringKey?: Key;
selectedStringKey?: Key | null;
disabledStringKeys?: Set<Key>;
onStringSelectionChange: (key: Key) => void;
onStringSelectionChange: (key: Key | null) => void;
}

/**
Expand Down Expand Up @@ -63,7 +63,7 @@ export function useStringifiedSelection({
);

const onStringSelectionChange = useCallback(
(key: Key): void => {
(key: Key | null): void => {
if (onChange == null) {
return;
}
Expand Down

0 comments on commit b50841d

Please sign in to comment.