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: ListView actions #448

Merged
merged 16 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
770 changes: 385 additions & 385 deletions package-lock.json

Large diffs are not rendered by default.

153 changes: 153 additions & 0 deletions plugins/ui/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,52 @@ my_checkbox = ui_checkbox()

![Checkbox](_assets/checkbox.png)

## ActionGroup (string values)
An ActionGroup is a grouping of ActionButtons that are related to one another.

```python
@ui.component
def ui_action_group():
[action, on_action] = ui.use_state()

return ui.flex(
ui.action_group(
"Aaa",
"Bbb",
"Ccc",
on_action=on_action,
),
ui.text(action),
direction="column",
)


my_action_group = ui_action_group()
```

## ActionMenu (string values)
ActionMenu combines an ActionButton with a Menu for simple "more actions" use cases.

```python
@ui.component
def ui_action_menu():
[action, on_action] = ui.use_state()

return ui.flex(
ui.action_menu(
"Aaa",
"Bbb",
"Ccc",
on_action=on_action,
),
ui.text(action),
direction="column",
)


my_action_menu = ui_action_menu()
```

## Picker (string values)

The `ui.picker` component can be used to select from a list of 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 Expand Up @@ -463,6 +509,113 @@ lv_table_source = ui_list_view_table_source()

![Use a list view to select from a table source](_assets/lv_table_source.png)

## ListView (list action group)

A list view can take a `list_action_group` as its `actions` prop.

```python
from deephaven import time_table, ui
import datetime

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

# `ui.list_view`` with `ui.list_action_group` actions
@ui.component
def ui_list_view_action_group():
value, set_value = ui.use_state(["key-2", "key-4", "key-5"])

action_item_keys, set_action_item_idx = ui.use_state(["", ""])
on_action = ui.use_callback(
lambda action_key, item_key: set_action_item_idx([action_key, str(item_key)]),
[],
)

lv = ui.list_view(
_column_types,
key_column="Id",
label_column="Display",
aria_label="List View",
on_change=set_value,
selected_keys=value,
actions=ui.list_action_group(
"Edit",
"Delete",
on_action=on_action,
),
)

text_selection = ui.text("Selection: " + ", ".join(map(str, value)))
text_action = ui.text("Action: " + " ".join(map(str, action_item_keys)))

return lv, text_selection, text_action


my_list_view_action_group = ui_list_view_action_group()
```

## ListView (list action menu)
A list view can take a `list_action_menu` as its `actions` prop.

```python
from deephaven import time_table, ui
import datetime

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

# `ui.list_view`` with `ui.list_action_menu` actions
@ui.component
def ui_list_view_action_menu():
value, set_value = ui.use_state(["key-2", "key-4", "key-5"])

action_item_keys, set_action_item_idx = ui.use_state(["", ""])
on_action = ui.use_callback(
lambda action_key, item_key: set_action_item_idx([action_key, str(item_key)]),
[],
)

lv = ui.list_view(
_column_types,
key_column="Id",
label_column="Display",
aria_label="List View",
on_change=set_value,
selected_keys=value,
actions=ui.list_action_menu(
"Edit",
"Delete",
on_action=on_action,
),
)

text_selection = ui.text("Selection: " + ", ".join(map(str, value)))
text_action = ui.text("Action: " + " ".join(map(str, action_item_keys)))

return lv, text_selection, text_action


my_list_view_action_menu = ui_list_view_action_menu()
```

## Form (two variables)

You can have state with multiple different variables in one component. This example creates a [text field](https://react-spectrum.adobe.com/react-spectrum/TextField.html) and a [slider](https://react-spectrum.adobe.com/react-spectrum/Slider.html), and we display the values of both of them.
Expand Down
4 changes: 4 additions & 0 deletions plugins/ui/src/deephaven/ui/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from .action_group import action_group
from .action_menu import action_menu
from .icon import icon
from .make_component import make_component as component
from .fragment import fragment
Expand All @@ -21,6 +23,8 @@

__all__ = [
"action_button",
"action_group",
"action_menu",
"button",
"button_group",
"checkbox",
Expand Down
13 changes: 13 additions & 0 deletions plugins/ui/src/deephaven/ui/components/action_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from ..elements import BaseElement


# TODO: pydocs for action_group #481
def action_group(*children, **props):
"""
An ActionGroup is a grouping of ActionButtons that are related to one another.

Args:
children: A list of Item or primitive elements.
**props: Any other ActionGroup prop.
"""
return BaseElement(f"deephaven.ui.components.ActionGroup", *children, **props)
Comment on lines +5 to +13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a follow-up TODO to explicitly add/document the props for this. @AkshatJawne could take that on, get him exposed to updating some Python code in a plugin.

@AkshatJawne as a precursor to what I mean, it would be similar to this change: https://github.com/deephaven/deephaven-plugins/pull/306/files
Right now this code accepts any keyword argument, but we want to explicitly add any prop that can be used and document it. They would generally map to the same props that are available on the UI component this maps to (in this case, the Spectrum ActionGroup).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, will take a look and get started!

13 changes: 13 additions & 0 deletions plugins/ui/src/deephaven/ui/components/action_menu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from ..elements import BaseElement


# TODO: pydocs for action_menu #482
def action_menu(*children, **props):
"""
ActionMenu combines an ActionButton with a Menu for simple "more actions" use cases.

Args:
children: A list of Item or primitive elements.
**props: Any other ActionMenu prop.
"""
return BaseElement(f"deephaven.ui.components.ActionMenu", *children, **props)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
ListActionGroupElement = Element


# TODO: pydocs for list_action_group #483
def list_action_group(
*children: ActionGroupItem,
on_action: Callable[[ActionKey, Key], None] | None = None,
Expand Down
1 change: 1 addition & 0 deletions plugins/ui/src/deephaven/ui/components/list_action_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ListActionMenuElement = Element


# TODO: pydocs for list_action_menu #484
def list_action_menu(
*children: ActionMenuItem,
on_action: Callable[[ActionKey, Key], None] | None = None,
Expand Down
30 changes: 15 additions & 15 deletions plugins/ui/src/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,21 @@
},
"dependencies": {
"@adobe/react-spectrum": "^3.34.1",
"@deephaven/chart": "^0.76.0",
"@deephaven/components": "^0.76.0",
"@deephaven/dashboard": "^0.76.0",
"@deephaven/dashboard-core-plugins": "^0.76.0",
"@deephaven/grid": "^0.76.0",
"@deephaven/icons": "^0.76.0",
"@deephaven/iris-grid": "^0.76.0",
"@deephaven/jsapi-bootstrap": "^0.76.0",
"@deephaven/jsapi-components": "^0.76.0",
"@deephaven/jsapi-types": "^1.0.0-dev0.34.1",
"@deephaven/log": "^0.76.0",
"@deephaven/plugin": "^0.76.0",
"@deephaven/react-hooks": "^0.76.0",
"@deephaven/redux": "^0.76.0",
"@deephaven/utils": "^0.76.0",
"@deephaven/chart": "^0.78.0",
"@deephaven/components": "^0.78.0",
"@deephaven/dashboard": "^0.78.0",
"@deephaven/dashboard-core-plugins": "^0.78.0",
"@deephaven/grid": "^0.78.0",
"@deephaven/icons": "^0.78.0",
"@deephaven/iris-grid": "^0.78.0",
"@deephaven/jsapi-bootstrap": "^0.78.0",
"@deephaven/jsapi-components": "^0.78.0",
"@deephaven/jsapi-types": "^1.0.0-dev0.34.2",
"@deephaven/log": "^0.78.0",
"@deephaven/plugin": "^0.78.0",
"@deephaven/react-hooks": "^0.78.0",
"@deephaven/redux": "^0.78.0",
"@deephaven/utils": "^0.78.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@react-types/shared": "^3.22.0",
"classnames": "^2.5.1",
Expand Down
39 changes: 39 additions & 0 deletions plugins/ui/src/js/src/elements/ActionGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
ActionGroup as DHActionGroup,
ActionGroupProps as DHActionGroupProps,
} from '@deephaven/components';
import {
SerializedSelectionProps,
useSelectionProps,
} from './spectrum/useSelectionProps';

export type SerializedActionGroupProps<T> = Omit<
DHActionGroupProps<T>,
'selectionMode' | 'onChange' | 'onSelectionChange'
> &
SerializedSelectionProps;

function ActionGroup<T>({
selectionMode: selectionModeMaybeUppercase,
onChange: serializedOnChange,
onSelectionChange: serializedOnSelectionChange,
...props
}: SerializedActionGroupProps<T>): JSX.Element {
const { selectionMode, onChange, onSelectionChange } = useSelectionProps({
selectionMode: selectionModeMaybeUppercase,
onChange: serializedOnChange,
onSelectionChange: serializedOnSelectionChange,
});

return (
<DHActionGroup
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
selectionMode={selectionMode}
onChange={onChange}
onSelectionChange={onSelectionChange}
/>
);
}

export default ActionGroup;
76 changes: 40 additions & 36 deletions plugins/ui/src/js/src/elements/ElementConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,43 @@ import type * as icons from '@deephaven/icons';
export const UI_COMPONENTS_NAMESPACE = 'deephaven.ui.components';
export const UI_ELEMENTS_NAMESPACE = 'deephaven.ui.elements';

/** Table */
export const UITABLE_ELEMENT_TYPE = `${UI_ELEMENTS_NAMESPACE}.UITable` as const;
export type UITableElementName = typeof UITABLE_ELEMENT_TYPE;

/** Layout */
export const PANEL_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Panel` as const;
export const ROW_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Row` as const;
export const COLUMN_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Column` as const;
export const STACK_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Stack` as const;
export const DASHBOARD_ELEMENT_NAME =
`${UI_COMPONENTS_NAMESPACE}.Dashboard` as const;

export type PanelElementType = typeof PANEL_ELEMENT_NAME;
export type RowElementType = typeof ROW_ELEMENT_NAME;
export type ColumnElementType = typeof COLUMN_ELEMENT_NAME;
export type StackElementType = typeof STACK_ELEMENT_NAME;
export type DashboardElementType = typeof DASHBOARD_ELEMENT_NAME;

/** Icons */
export const ICON_ELEMENT_TYPE_PREFIX = 'deephaven.ui.icons.';
export type IconElementName =
`${typeof ICON_ELEMENT_TYPE_PREFIX}${keyof typeof icons}`;

/** HTML */
export const HTML_ELEMENT_NAME_PREFIX = 'deephaven.ui.html.';
export type HTMLElementType =
`${typeof HTML_ELEMENT_NAME_PREFIX}${keyof ReactHTML}`;

/** Specific Components */
export const FRAGMENT_ELEMENT_NAME =
`${UI_COMPONENTS_NAMESPACE}.Fragment` as const;
export const ITEM_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Item` as const;
export const LIST_VIEW_NAME = `${UI_COMPONENTS_NAMESPACE}.ListView` as const;
export const PICKER_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Picker` as const;
export const SECTION_ELEMENT_NAME =
`${UI_COMPONENTS_NAMESPACE}.Section` as const;
const uiComponentName = <T extends string>(name: T) =>
`${UI_COMPONENTS_NAMESPACE}.${name}` as const;

const uiElementName = <T extends string>(name: T) =>
`${UI_ELEMENTS_NAMESPACE}.${name}` as const;

export const ELEMENT_NAME = {
/** Elements */
uiTable: uiElementName('UITable'),

/** Layout Components */
column: uiComponentName('Column'),
dashboard: uiComponentName('Dashboard'),
panel: uiComponentName('Panel'),
row: uiComponentName('Row'),
stack: uiComponentName('Stack'),

/** Other Components */
actionGroup: uiComponentName('ActionGroup'),
actionMenu: uiComponentName('ActionMenu'),
fragment: uiComponentName('Fragment'),
item: uiComponentName('Item'),
listActionGroup: uiComponentName('ListActionGroup'),
listActionMenu: uiComponentName('ListActionMenu'),
listView: uiComponentName('ListView'),
picker: uiComponentName('Picker'),
section: uiComponentName('Section'),
} as const;

export type ElementName = typeof ELEMENT_NAME;

export const ELEMENT_PREFIX = {
icon: 'deephaven.ui.icons.' as const,
html: 'deephaven.ui.html.' as const,
};

export type ElementPrefix = {
icon: `${typeof ELEMENT_PREFIX.icon}${keyof typeof icons}`;
html: `${typeof ELEMENT_PREFIX.html}${keyof ReactHTML}`;
};
Loading
Loading