Skip to content

Commit

Permalink
feat: ListView actions (#448)
Browse files Browse the repository at this point in the history
- ui.action_group
- ui.action_menu
- ui.list_action_group
- ui.list_action_menu
- Cleaned up some element name utils

Testing
- I tested action_group and action_menu in isolation
- I tested list_action_group and list_action_menu in `list_view` actions
prop as well as in `item_table_source`

resolves #445
  • Loading branch information
bmingles authored May 23, 2024
1 parent 94ef543 commit ca65b69
Show file tree
Hide file tree
Showing 24 changed files with 867 additions and 600 deletions.
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)
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

0 comments on commit ca65b69

Please sign in to comment.