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.
Split out usePickerProps hook and wired up ComboBox (deephaven#2074)
- Loading branch information
Showing
7 changed files
with
225 additions
and
144 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 |
---|---|---|
@@ -1,10 +1,16 @@ | ||
export { | ||
ActionBar, | ||
type SpectrumActionBarProps as ActionBarProps, | ||
// ComboBox is exported from ComboBox.tsx as a custom DH component. Re-exporting | ||
// the Spectrum props type for upstream consumers that need to compose prop types. | ||
type SpectrumComboBoxProps, | ||
// ListBox - we aren't planning to support this component | ||
MenuTrigger, | ||
type SpectrumMenuTriggerProps as MenuTriggerProps, | ||
// TableView - we aren't planning to support this component | ||
// Picker is exported from Picker.tsx as a custom DH component. Re-exporting | ||
// the Spectrum props type for upstream consumers that need to compose prop types. | ||
type SpectrumPickerProps, | ||
TagGroup, | ||
type SpectrumTagGroupProps as TagGroupProps, | ||
} from '@adobe/react-spectrum'; |
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,19 @@ | ||
import { ComboBoxNormalized } from '@deephaven/components'; | ||
import { dh as DhType } from '@deephaven/jsapi-types'; | ||
import { usePickerProps } from './utils'; | ||
|
||
export interface ComboBoxProps { | ||
table: DhType.Table; | ||
} | ||
|
||
export function ComboBox(props: ComboBoxProps): JSX.Element { | ||
const pickerProps = usePickerProps(props); | ||
return ( | ||
<ComboBoxNormalized | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...pickerProps} | ||
/> | ||
); | ||
} | ||
|
||
export default ComboBox; |
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,27 @@ | ||
import { | ||
NormalizedItem, | ||
PickerPropsT, | ||
SpectrumPickerProps, | ||
} from '@deephaven/components'; | ||
import { dh as DhType } from '@deephaven/jsapi-types'; | ||
import { Settings } from '@deephaven/jsapi-utils'; | ||
|
||
export type PickerWithTableProps<TProps> = Omit< | ||
PickerPropsT<TProps>, | ||
'children' | ||
> & { | ||
table: DhType.Table; | ||
/* The column of values to use as item keys. Defaults to the first column. */ | ||
keyColumn?: string; | ||
/* The column of values to display as primary text. Defaults to the `keyColumn` value. */ | ||
labelColumn?: string; | ||
|
||
/* The column of values to map to icons. */ | ||
iconColumn?: string; | ||
|
||
settings?: Settings; | ||
}; | ||
|
||
export type PickerProps = PickerWithTableProps< | ||
SpectrumPickerProps<NormalizedItem> | ||
>; |
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 './ComboBox'; | ||
export * from './ListView'; | ||
export * from './Picker'; | ||
export * from './PickerProps'; |
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,3 @@ | ||
export * from './itemUtils'; | ||
export * from './useItemRowDeserializer'; | ||
export * from './usePickerProps'; |
164 changes: 164 additions & 0 deletions
164
packages/jsapi-components/src/spectrum/utils/usePickerProps.ts
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,164 @@ | ||
import { useCallback, useEffect, useMemo, useState } from 'react'; | ||
import { | ||
ItemKey, | ||
NormalizedItem, | ||
NormalizedItemData, | ||
NormalizedSection, | ||
NormalizedSectionData, | ||
usePickerItemScale, | ||
} from '@deephaven/components'; | ||
import { dh as DhType } from '@deephaven/jsapi-types'; | ||
import Log from '@deephaven/log'; | ||
import { PICKER_TOP_OFFSET } from '@deephaven/utils'; | ||
import useFormatter from '../../useFormatter'; | ||
import type { PickerWithTableProps } from '../PickerProps'; | ||
import { getItemKeyColumn } from './itemUtils'; | ||
import useItemRowDeserializer from './useItemRowDeserializer'; | ||
import useGetItemIndexByValue from '../../useGetItemIndexByValue'; | ||
import useViewportData from '../../useViewportData'; | ||
|
||
const log = Log.module('jsapi-components.usePickerProps'); | ||
|
||
/** Props that are derived by `usePickerProps`. */ | ||
export type UsePickerDerivedProps = { | ||
normalizedItems: (NormalizedItem | NormalizedSection)[]; | ||
showItemIcons: boolean; | ||
getInitialScrollPosition: () => Promise<number | null>; | ||
onChange: (key: ItemKey | null) => void; | ||
onScroll: (event: Event) => void; | ||
}; | ||
|
||
/** | ||
* Props that are passed through untouched. (should exclude all of the | ||
* destructured props passed into `usePickerProps` that are not in the spread | ||
* ...props) | ||
) */ | ||
export type UsePickerPassthroughProps<TProps> = Omit< | ||
PickerWithTableProps<TProps>, | ||
| 'table' | ||
| 'keyColumn' | ||
| 'labelColumn' | ||
| 'iconColumn' | ||
| 'settings' | ||
| 'onChange' | ||
| 'onSelectionChange' | ||
>; | ||
|
||
/** Props returned by `usePickerProps` hook. */ | ||
export type UsePickerProps<TProps> = UsePickerDerivedProps & | ||
UsePickerPassthroughProps<TProps>; | ||
|
||
export function usePickerProps<TProps>({ | ||
table, | ||
keyColumn: keyColumnName, | ||
labelColumn: labelColumnName, | ||
iconColumn: iconColumnName, | ||
settings, | ||
onChange, | ||
onSelectionChange, | ||
...props | ||
}: PickerWithTableProps<TProps>): UsePickerProps<TProps> { | ||
const { itemHeight } = usePickerItemScale(); | ||
|
||
const { getFormattedString: formatValue } = useFormatter(settings); | ||
|
||
// `null` is a valid value for `selectedKey` in controlled mode, so we check | ||
// for explicit `undefined` to identify uncontrolled mode. | ||
const isUncontrolled = props.selectedKey === undefined; | ||
const [uncontrolledSelectedKey, setUncontrolledSelectedKey] = useState< | ||
ItemKey | null | undefined | ||
>(props.defaultSelectedKey); | ||
|
||
const keyColumn = useMemo( | ||
() => getItemKeyColumn(table, keyColumnName), | ||
[keyColumnName, table] | ||
); | ||
|
||
const deserializeRow = useItemRowDeserializer({ | ||
table, | ||
iconColumnName, | ||
keyColumnName, | ||
labelColumnName, | ||
formatValue, | ||
}); | ||
|
||
const getItemIndexByValue = useGetItemIndexByValue({ | ||
table, | ||
columnName: keyColumn.name, | ||
value: isUncontrolled ? uncontrolledSelectedKey : props.selectedKey, | ||
}); | ||
|
||
const getInitialScrollPosition = useCallback(async () => { | ||
const index = await getItemIndexByValue(); | ||
|
||
if (index == null) { | ||
return null; | ||
} | ||
|
||
return index * itemHeight + PICKER_TOP_OFFSET; | ||
}, [getItemIndexByValue, itemHeight]); | ||
|
||
const { viewportData, onScroll, setViewport } = useViewportData< | ||
NormalizedItemData | NormalizedSectionData, | ||
DhType.Table | ||
>({ | ||
reuseItemsOnTableResize: true, | ||
table, | ||
itemHeight, | ||
deserializeRow, | ||
}); | ||
|
||
const normalizedItems = viewportData.items as ( | ||
| NormalizedItem | ||
| NormalizedSection | ||
)[]; | ||
|
||
useEffect( | ||
// Set viewport to include the selected item so that its data will load and | ||
// the real `key` will be available to show the selection in the UI. | ||
function setViewportFromSelectedKey() { | ||
let isCanceled = false; | ||
|
||
getItemIndexByValue() | ||
.then(index => { | ||
if (index == null || isCanceled) { | ||
return; | ||
} | ||
|
||
setViewport(index); | ||
}) | ||
.catch(err => { | ||
log.error('Error setting viewport from selected key', err); | ||
}); | ||
|
||
return () => { | ||
isCanceled = true; | ||
}; | ||
}, | ||
[getItemIndexByValue, settings, setViewport] | ||
); | ||
|
||
const onSelectionChangeInternal = useCallback( | ||
(key: ItemKey | null): void => { | ||
// If our component is uncontrolled, track the selected key internally | ||
// so that we can scroll to the selected item if the user re-opens | ||
if (isUncontrolled) { | ||
setUncontrolledSelectedKey(key); | ||
} | ||
|
||
(onChange ?? onSelectionChange)?.(key); | ||
}, | ||
[isUncontrolled, onChange, onSelectionChange] | ||
); | ||
|
||
return { | ||
...props, | ||
normalizedItems, | ||
showItemIcons: iconColumnName != null, | ||
getInitialScrollPosition, | ||
onChange: onSelectionChangeInternal, | ||
onScroll, | ||
}; | ||
} | ||
|
||
export default usePickerProps; |