Skip to content

Commit

Permalink
Support passing NormalizedPickerItem[] to Picker (#1858)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmingles committed Mar 19, 2024
1 parent c8781da commit 1ca0015
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 36 deletions.
66 changes: 39 additions & 27 deletions packages/components/src/spectrum/picker/Picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { PickerItemContent } from './PickerItemContent';
import { Item, Section } from '../shared';

export type PickerProps = {
children: PickerItemOrSection | PickerItemOrSection[];
children:
| PickerItemOrSection
| PickerItemOrSection[]
| NormalizedPickerItem[];
/** Can be set to true or a TooltipOptions to enable item tooltips */
tooltip?: boolean | TooltipOptions;
/** The currently selected key in the collection (controlled). */
Expand Down Expand Up @@ -99,31 +102,38 @@ export function Picker({
);

const renderItem = useCallback(
({ key, item }: NormalizedPickerItem) => (
// 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.
<Item
key={key as Key}
textValue={
item?.textValue === '' || item?.textValue == null
? 'Empty'
: item.textValue
}
>
{item?.content == null ? null : (
<>
<PickerItemContent>{item.content}</PickerItemContent>
{tooltipOptions == null || item.content === '' ? null : (
<Tooltip options={tooltipOptions}>
{createTooltipContent(item.content)}
</Tooltip>
)}
</>
)}
</Item>
),
(normalizedItem: NormalizedPickerItem) => {
// In Windowed data scenarios, the `item` is loaded asynchronously.
// Fallback to the top-level `key` if the `item` is not yet available.
const key = normalizedItem.item?.key ?? normalizedItem.key;
const { item } = normalizedItem;

return (
// 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.
<Item
key={key as Key}
textValue={
item?.textValue === '' || item?.textValue == null
? 'Empty'
: item.textValue
}
>
{item?.content == null ? null : (
<>
<PickerItemContent>{item.content}</PickerItemContent>
{tooltipOptions == null || item.content === '' ? null : (
<Tooltip options={tooltipOptions}>
{createTooltipContent(item.content)}
</Tooltip>
)}
</>
)}
</Item>
);
},
[tooltipOptions]
);

Expand All @@ -150,7 +160,9 @@ export function Picker({
if (isNormalizedPickerSection(itemOrSection)) {
return (
<Section
key={itemOrSection.key}
// In Windowed data scenarios, the `item` is loaded asynchronously
// Fallback to the top-level `key` if the `item` is not yet available
key={itemOrSection.item?.key ?? itemOrSection.key}
title={itemOrSection.item?.title}
items={itemOrSection.item?.items}
>
Expand Down
44 changes: 35 additions & 9 deletions packages/components/src/spectrum/picker/PickerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,31 @@ export type PickerItemKey = Key | boolean;
*/
export type PickerSelectionChangeHandler = (key: PickerItemKey) => void;

export interface NormalizedPickerItemData {
key?: PickerItemKey;
content: ReactNode;
textValue?: string;
}

export interface NormalizedPickerSectionData {
key?: Key;
title?: ReactNode;
items: NormalizedPickerItem[];
}

/**
* The Picker supports a variety of item types, including strings, numbers,
* booleans, and more complex React elements. This type represents a normalized
* form to make rendering items simpler and keep the logic of transformation
* in separate util methods.
*/
export type NormalizedPickerItem = KeyedItem<
{
content: ReactNode;
textValue?: string;
},
NormalizedPickerItemData,
PickerItemKey | undefined
>;

export type NormalizedPickerSection = KeyedItem<
{
title?: ReactNode;
items: NormalizedPickerItem[];
},
NormalizedPickerSectionData,
Key | undefined
>;

Expand Down Expand Up @@ -108,6 +114,17 @@ export function isPickerItemOrSection(
);
}

export function isNormalizedPickerItemList(
node: PickerItemOrSection | PickerItemOrSection[] | NormalizedPickerItem[]
): node is NormalizedPickerItem[] {
return (
Array.isArray(node) &&
node.length > 0 &&
!isPickerItemOrSection(node[0]) &&
'key' in node[0]
);
}

/**
* Determine if an object is a normalized Picker section.
* @param maybeNormalizedPickerSection The object to check
Expand Down Expand Up @@ -228,11 +245,20 @@ function normalizePickerItem(
* @returns An array of normalized picker items
*/
export function normalizePickerItemList(
itemsOrSections: PickerItemOrSection | PickerItemOrSection[]
itemsOrSections:
| PickerItemOrSection
| PickerItemOrSection[]
| NormalizedPickerItem[]
): (NormalizedPickerItem | NormalizedPickerSection)[] {
// If already normalized, just return as-is
if (isNormalizedPickerItemList(itemsOrSections)) {
return itemsOrSections;
}

const itemsArray = Array.isArray(itemsOrSections)
? itemsOrSections
: [itemsOrSections];

return itemsArray.map(normalizePickerItem);
}

Expand Down

0 comments on commit 1ca0015

Please sign in to comment.