1- import { Key , ReactElement , ReactNode } from 'react' ;
2- import type { SpectrumPickerProps } from '@adobe/react-spectrum' ;
3- import type { ItemProps } from '@react-types/shared' ;
1+ import { isValidElement , Key , ReactElement , ReactNode } from 'react' ;
2+ import { Item , Section , SpectrumPickerProps } from '@adobe/react-spectrum' ;
3+ import type {
4+ ItemProps ,
5+ ItemRenderer ,
6+ SectionProps ,
7+ } from '@react-types/shared' ;
48import { PopperOptions } from '../../popper' ;
59
10+ export type SectionPropsNoItemRenderer < T > = Omit <
11+ SectionProps < T > ,
12+ 'children'
13+ > & {
14+ children : Exclude < SectionProps < T > [ 'children' ] , ItemRenderer < T > > ;
15+ } ;
16+
617export type ItemElement = ReactElement < ItemProps < unknown > > ;
18+ export type SectionElement = ReactElement < SectionPropsNoItemRenderer < unknown > > ;
719export type PickerItem = number | string | boolean | ItemElement ;
20+ export type PickerSection = SectionElement ;
21+ export type PickerItemOrSection = PickerItem | PickerSection ;
822
923/**
1024 * Augment the Spectrum selection key type to include boolean values.
@@ -32,32 +46,64 @@ export interface NormalizedPickerItem {
3246 textValue : string ;
3347}
3448
49+ export interface NormalizedPickerSection {
50+ key : Key ;
51+ title : ReactNode ;
52+ items : NormalizedPickerItem [ ] ;
53+ }
54+
3555export type NormalizedSpectrumPickerProps =
3656 SpectrumPickerProps < NormalizedPickerItem > ;
3757
3858export type TooltipOptions = { placement : PopperOptions [ 'placement' ] } ;
3959
60+ export function isSectionElement < T > (
61+ node : ReactNode
62+ ) : node is ReactElement < SectionProps < T > > {
63+ return isValidElement < SectionProps < T > > ( node ) && node . type === Section ;
64+ }
65+
66+ export function isItemElement < T > (
67+ node : ReactNode
68+ ) : node is ReactElement < ItemProps < T > > {
69+ return isValidElement < ItemProps < T > > ( node ) && node . type === Item ;
70+ }
71+
4072/**
4173 * Determine the `key` of a picker item.
42- * @param item The picker item
74+ * @param item The picker item or section
4375 * @returns A `PickerItemKey` for the picker item
4476 */
45- function normalizeItemKey ( item : PickerItem ) : PickerItemKey {
77+ function normalizeItemKey ( item : PickerItem ) : PickerItemKey ;
78+ function normalizeItemKey ( item : PickerSection ) : Key ;
79+ function normalizeItemKey (
80+ item : PickerItem | PickerSection
81+ ) : Key | PickerItemKey {
4682 // string, number, or boolean
4783 if ( typeof item !== 'object' ) {
48- return item ;
84+ return item as PickerItemKey ;
4985 }
5086
51- // `ItemElement` with `key` prop set
87+ // If `key` prop is explicitly set
5288 if ( item . key != null ) {
5389 return item . key ;
5490 }
5591
92+ if ( isSectionElement ( item ) ) {
93+ if ( typeof item . props . title === 'string' ) {
94+ return item . props . title ;
95+ }
96+ } else if ( isItemElement ( item ) ) {
97+ if ( item . props . textValue != null ) {
98+ return item . props . textValue ;
99+ }
100+ }
101+
56102 if ( typeof item . props . children === 'string' ) {
57103 return item . props . children ;
58104 }
59105
60- return item . props . textValue ?? '' ;
106+ return '' ;
61107}
62108
63109/**
@@ -86,7 +132,25 @@ function normalizeTextValue(item: PickerItem): string {
86132 * @param item item to normalize
87133 * @returns NormalizedPickerItem object
88134 */
89- function normalizePickerItem ( item : PickerItem ) : NormalizedPickerItem {
135+ function normalizePickerItem (
136+ item : PickerItemOrSection
137+ ) : NormalizedPickerItem | NormalizedPickerSection {
138+ if ( isSectionElement ( item ) ) {
139+ const key = normalizeItemKey ( item ) ;
140+ const { title } = item . props ;
141+
142+ const items = normalizePickerItemList ( item . props . children ) . filter (
143+ // We don't support nested section elements
144+ childItem => ! isSectionElement ( childItem )
145+ ) as NormalizedPickerItem [ ] ;
146+
147+ return {
148+ key,
149+ title,
150+ items,
151+ } ;
152+ }
153+
90154 const key = normalizeItemKey ( item ) ;
91155 const content = typeof item === 'object' ? item . props . children : String ( item ) ;
92156 const textValue = normalizeTextValue ( item ) ;
@@ -104,8 +168,8 @@ function normalizePickerItem(item: PickerItem): NormalizedPickerItem {
104168 * @returns An array of normalized picker items
105169 */
106170export function normalizePickerItemList (
107- items : PickerItem | PickerItem [ ]
108- ) : NormalizedPickerItem [ ] {
171+ items : PickerItemOrSection | PickerItemOrSection [ ]
172+ ) : ( NormalizedPickerItem | NormalizedPickerSection ) [ ] {
109173 const itemsArray = Array . isArray ( items ) ? items : [ items ] ;
110174 return itemsArray . map ( normalizePickerItem ) ;
111175}
0 commit comments