Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: UI ComboBox component #588

Merged
merged 5 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 79 additions & 4 deletions plugins/ui/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,15 +348,15 @@ def ui_picker_table():
column_types,
label="Text",
on_change=set_value,
selected_keys=value,
selected_key=value,
)

text = ui.text(f"Selection: {value}")

return ui.flex(pick_table, text, direction="column", margin=10, gap=10)


pick_table = ui_picker_table()
my_picker_table = ui_picker_table()
```

![Use a picker to select from a table](_assets/pick_table.png)
Expand Down Expand Up @@ -391,19 +391,94 @@ def ui_picker_table_source():
ui.item_table_source(column_types, key_column="Id", label_column="Display"),
label="Text",
on_change=set_value,
selected_keys=value,
selected_key=value,
)

text = ui.text(f"Selection: {value}")

return ui.flex(pick_table, text, direction="column", margin=10, gap=10)


pick_table_source = ui_picker_table_source()
my_picker_table_source = ui_picker_table_source()
```

![Use a picker to select from a table source](_assets/pick_table_source.png)

## ComboBox (string values)

The `ui.combo_box` component can be used to select from a list of items. It also provides a search field to filter available results. Note that the search behavior differs slightly for different data types.
- Numeric types - only support exact match
- Text based data types - support partial search matching
- Date types support searching by different date parts (e.g. `2024`, `2024-01`, `2024-01-02`, `2024-01-02 00`, `2024-07-06 00:43`, `2024-07-06 00:43:14`, `2024-07-06 00:43:14.247`)

Here's a basic example for selecting from a list of string values and displaying the selected key in a text field.

```python
from deephaven import ui


@ui.component
def ui_combo_box():
value, set_value = ui.use_state("")

combo = ui.combo_box(
"Text 1",
"Text 2",
"Text 3",
label="Text",
on_selection_change=set_value,
selected_key=value,
)

text = ui.text("Selection: " + str(value))

return combo, text


my_combo_box = ui_combo_box()
```

## ComboBox (item table source)

A combo_box can also take an `item_table_source`. It will use the columns specified.
bmingles marked this conversation as resolved.
Show resolved Hide resolved

```python
import deephaven.ui as ui
from deephaven import time_table
import datetime

# Ticking table with initial row count of 200 that adds a row every second
initial_row_count = 200
_table = time_table(
"PT1S",
start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count),
).update(
[
"Id=new Integer(i)",
"Display=new String(`Display `+i)",
]
)


@ui.component
def ui_combo_box_item_table_source(table):
value, set_value = ui.use_state("")

combo = ui.combo_box(
ui.item_table_source(table, key_column="Id", label_column="Display"),
label="Text",
on_change=set_value,
selected_key=value,
)

text = ui.text(f"Selection: {value}")

return combo, text


my_combo_box_item_table_source = ui_combo_box_item_table_source(_table)
```

## ListView (string values)

A list view that can be used to create a list of selectable items. Here's a basic example for selecting from a list of string values and displaying the selected key in a text field.
Expand Down
1 change: 0 additions & 1 deletion plugins/ui/src/deephaven/ui/components/combo_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ def combo_box(
aria_details: str | None = None,
UNSAFE_class_name: str | None = None,
UNSAFE_style: CSSProperties | None = None,
**props: Any,
) -> ComboBoxElement:
"""
A combo box that can be used to search or select from a list. Children should be one of five types:
Expand Down
44 changes: 44 additions & 0 deletions plugins/ui/src/js/src/elements/ComboBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useSelector } from 'react-redux';
import {
ComboBox as DHComboBox,
ComboBoxProps as DHComboBoxProps,
} from '@deephaven/components';
import {
ComboBox as DHComboBoxJSApi,
ComboBoxProps as DHComboBoxJSApiProps,
} from '@deephaven/jsapi-components';
import { isElementOfType } from '@deephaven/react-hooks';
import { getSettings, RootState } from '@deephaven/redux';
import {
SerializedPickerProps,
usePickerProps,
WrappedDHPickerJSApiProps,
} from './hooks/usePickerProps';
import ObjectView from './ObjectView';
import { useReExportedTable } from './hooks/useReExportedTable';

export function ComboBox(
props: SerializedPickerProps<
DHComboBoxProps | WrappedDHPickerJSApiProps<DHComboBoxJSApiProps>
>
): JSX.Element | null {
const settings = useSelector(getSettings<RootState>);
const { children, ...pickerProps } = usePickerProps(props);

const isObjectView = isElementOfType(children, ObjectView);
const table = useReExportedTable(children);

if (isObjectView) {
return (
table && (
// eslint-disable-next-line react/jsx-props-no-spreading
<DHComboBoxJSApi {...pickerProps} table={table} settings={settings} />
)
);
}

// eslint-disable-next-line react/jsx-props-no-spreading
return <DHComboBox {...pickerProps}>{children}</DHComboBox>;
}

export default ComboBox;
22 changes: 18 additions & 4 deletions plugins/ui/src/js/src/elements/Picker.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import { useSelector } from 'react-redux';
import { Picker as DHPicker } from '@deephaven/components';
import { Picker as DHPickerJSApi } from '@deephaven/jsapi-components';
import {
Picker as DHPicker,
PickerProps as DHPickerProps,
} from '@deephaven/components';
import {
Picker as DHPickerJSApi,
PickerProps as DHPickerJSApiProps,
} from '@deephaven/jsapi-components';
import { isElementOfType } from '@deephaven/react-hooks';
import { getSettings, RootState } from '@deephaven/redux';
import { SerializedPickerProps, usePickerProps } from './hooks/usePickerProps';
import {
SerializedPickerProps,
usePickerProps,
WrappedDHPickerJSApiProps,
} from './hooks/usePickerProps';
import ObjectView from './ObjectView';
import useReExportedTable from './hooks/useReExportedTable';

export function Picker(props: SerializedPickerProps): JSX.Element | null {
export function Picker(
props: SerializedPickerProps<
DHPickerProps | WrappedDHPickerJSApiProps<DHPickerJSApiProps>
>
): JSX.Element | null {
const settings = useSelector(getSettings<RootState>);
const { children, ...pickerProps } = usePickerProps(props);

Expand Down
36 changes: 26 additions & 10 deletions plugins/ui/src/js/src/elements/hooks/usePickerProps.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { PickerProps as DHPickerProps } from '@deephaven/components';
import { PickerProps as DHPickerJSApiProps } from '@deephaven/jsapi-components';
import { ReactElement } from 'react';
import ObjectView, { ObjectViewProps } from '../ObjectView';
import {
DeserializedFocusEventCallback,
SerializedFocusEventCallback,
useFocusEventCallback,
} from './useFocusEventCallback';
import {
DeserializedKeyboardEventCallback,
SerializedKeyboardEventCallback,
useKeyboardEventCallback,
} from './useKeyboardEventCallback';
Expand All @@ -25,28 +25,44 @@ export interface SerializedPickerEventProps {
onKeyUp?: SerializedKeyboardEventCallback;
}

type WrappedDHPickerJSApiProps = Omit<DHPickerJSApiProps, 'table'> & {
export interface DeserializedPickerEventProps {
/** Handler that is called when the element receives focus. */
onFocus?: DeserializedFocusEventCallback;

/** Handler that is called when the element loses focus. */
onBlur?: DeserializedFocusEventCallback;

/** Handler that is called when a key is pressed */
onKeyDown?: DeserializedKeyboardEventCallback;

/** Handler that is called when a key is released */
onKeyUp?: DeserializedKeyboardEventCallback;
}

export type WrappedDHPickerJSApiProps<TProps> = Omit<TProps, 'table'> & {
children: ReactElement<ObjectViewProps, typeof ObjectView>;
};

export type SerializedPickerProps = (
| DHPickerProps
| WrappedDHPickerJSApiProps
) &
SerializedPickerEventProps;
export type SerializedPickerProps<TProps> = TProps & SerializedPickerEventProps;

export type DeserializedPickerProps<TProps> = Omit<
TProps,
keyof SerializedPickerEventProps
> &
DeserializedPickerEventProps;

/**
* Wrap Picker props with the appropriate serialized event callbacks.
* @param props Props to wrap
* @returns Wrapped props
*/
export function usePickerProps({
export function usePickerProps<TProps>({
onFocus,
onBlur,
onKeyDown,
onKeyUp,
...otherProps
}: SerializedPickerProps): DHPickerProps | WrappedDHPickerJSApiProps {
}: SerializedPickerProps<TProps>): DeserializedPickerProps<TProps> {
const serializedOnFocus = useFocusEventCallback(onFocus);
const serializedOnBlur = useFocusEventCallback(onBlur);
const serializedOnKeyDown = useKeyboardEventCallback(onKeyDown);
Expand Down
1 change: 1 addition & 0 deletions plugins/ui/src/js/src/elements/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './ActionButton';
export * from './ActionGroup';
export * from './Button';
export * from './ComboBox';
export * from './Form';
export * from './hooks';
export * from './HTMLElementView';
Expand Down
1 change: 1 addition & 0 deletions plugins/ui/src/js/src/elements/model/ElementConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const ELEMENT_NAME = {
button: uiComponentName('Button'),
buttonGroup: uiComponentName('ButtonGroup'),
checkbox: uiComponentName('Checkbox'),
comboBox: uiComponentName('ComboBox'),
content: uiComponentName('Content'),
contextualHelp: uiComponentName('ContextualHelp'),
flex: uiComponentName('Flex'),
Expand Down
2 changes: 2 additions & 0 deletions plugins/ui/src/js/src/widget/WidgetUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
ActionButton,
ActionGroup,
Button,
ComboBox,
Form,
IllustratedMessage,
ListView,
Expand Down Expand Up @@ -97,6 +98,7 @@ export const elementComponentMap = {
[ELEMENT_NAME.button]: Button,
[ELEMENT_NAME.buttonGroup]: ButtonGroup,
[ELEMENT_NAME.checkbox]: Checkbox,
[ELEMENT_NAME.comboBox]: ComboBox,
[ELEMENT_NAME.content]: Content,
[ELEMENT_NAME.contextualHelp]: ContextualHelp,
[ELEMENT_NAME.flex]: Flex,
Expand Down
Loading