diff --git a/plugins/ui/docs/components/labeled_value.md b/plugins/ui/docs/components/labeled_value.md new file mode 100644 index 000000000..02aacfba7 --- /dev/null +++ b/plugins/ui/docs/components/labeled_value.md @@ -0,0 +1,108 @@ +# Labeled Value + +A labeled value displays a non-editable value with a label. + +## Example + +```python +from deephaven import ui + + +my_labeled_value_basic = ui.labeled_value(label="File name", value="Budget.xls") +``` + + +## Value + +A labeled value accepts numbers, strings, and lists of strings in the `value` prop. + +```python +from deephaven import ui + + +@ui.component +def ui_labeled_value_examples(): + return [ + ui.labeled_value(label="File name", value="Budget.xls"), + ui.labeled_value(label="Number of expenses in Budget file", value=123), + ui.labeled_value( + label="Pizza toppings", value=["Pizza", "Pineapple", "Mushroom", "Garlic"] + ), + ] + + +my_labeled_value_values_examples = ui_labeled_value_examples() +``` + +## Numbers + +When passing a number into a labeled value, the `format_options` prop dictates how the value is displayed. There are 3 styles supported by this parameter: Percentage, Currency, and Units. + +Note that this prop is compatible with the option parameter of [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat). + +```python +from deephaven import ui + + +@ui.component +def ui_labeled_value_numbers_example(): + return [ + ui.labeled_value( + label="Percent completed", + value=0.89, + format_options={"style": "percent"}, + ), + ui.labeled_value( + label="Withdrawal amount", + value=2350.50, + format_options={"style": "currency", "currency": "USD"}, + ), + ui.labeled_value( + label="Height of Burj Khalifa", + value=32600, + format_options={"style": "unit", "unit": "inch"}, + ), + ] + + +my_labeled_value_numbers_example = ui_labeled_value_numbers_example() +``` + + +## Label position + +By default, the label is positioned above the labeled value, but it can be moved to the side using the `label_position` prop. + +```python +from deephaven import ui + + +my_labeled_value_label_position_example = ui.labeled_value( + label="File name", value="Onboarding.pdf", label_position="side", label_align="end" +) +``` + + +## Contextual Help + +Using the `contextual_help` prop, a `ui.contextual_help` can be placed next to the labeled value to provide additional information. + +```python +from deephaven import ui + + +my_labeled_value_contextual_help_example = ui.labeled_value( + label="File name", + value="Onboarding.pdf", + contextual_help=ui.contextual_help( + heading="Info about the onboarding document", content="Sample content" + ), +) +``` + + +## API reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.labeled_value +``` \ No newline at end of file diff --git a/plugins/ui/docs/components/tag_group.md b/plugins/ui/docs/components/tag_group.md new file mode 100644 index 000000000..54f8fce2d --- /dev/null +++ b/plugins/ui/docs/components/tag_group.md @@ -0,0 +1,272 @@ +# Tag Group + +Tags allow users to categorize content. They can represent keywords or people, and are grouped to describe an item or a search request. + +## Example + +```python +from deephaven import ui + + +tag_group_example = ui.tag_group( + ui.item("News", key="news"), + ui.item("Travel", key="travel"), + ui.item("Gaming", key="gaming"), + ui.item("Shopping", key="shopping"), +) +``` + +## Content + +`tag_group` accepts `item` elements as children, each with a `key` prop. Basic usage of `tag_group`, seen in the example above, shows multiple items populated with a string. + +## Labeling + +To provide a visual label for the tag group, use the `label` prop. + +```python +from deephaven import ui + + +tag_group_label_example = ui.tag_group( + ui.item("News", key="news"), + ui.item("Travel", key="travel"), + ui.item("Gaming", key="gaming"), + ui.item("Shopping", key="shopping"), + label="Categories", +) +``` + +### Label Position + +By default, the position of the label is above the tag group, but it can be moved to the side using the `label_position` prop. + +```python +from deephaven import ui + + +tag_group_label_example = ui.tag_group( + ui.item("News", key="news"), + ui.item("Travel", key="travel"), + ui.item("Gaming", key="gaming"), + ui.item("Shopping", key="shopping"), + label="Categories", + label_position="side", +) +``` + +### Label Alignment + +By default, the label is horizontally aligned to the start of the tag group element, but it can be moved to the end by using the `label_align` prop. + +```python +from deephaven import ui + + +tag_group_label_example = ui.tag_group( + ui.item("News", key="news"), + ui.item("Travel", key="travel"), + ui.item("Gaming", key="gaming"), + ui.item("Shopping", key="shopping"), + label="Categories", + label_align="end", +) +``` + +## Events + +Removing tags can be enabled by providing the `on_remove` prop to the tag group, which will receive the set of keys to remove. + +```python +from deephaven import ui + + +@ui.component +def tag_group_remove_example(): + items, set_items = ui.use_state( + [ + ui.item("News", key="news"), + ui.item("Travel", key="travel"), + ui.item("Gaming", key="gaming"), + ui.item("Shopping", key="shopping"), + ] + ) + + return ( + ui.tag_group( + *items, + on_remove=lambda keys: set_items( + [item for item in items if item.key not in keys] + ) + ), + ) + + +my_tag_group_remove_example = tag_group_remove_example() +``` + +Use the prop `action_label` to display an action button with that label at the end of the tags. The custom action that will be performed is specified by the `on_action` prop. + +```python +from deephaven import ui + + +@ui.component +def tag_group_action_example(): + items, set_items = ui.use_state( + [ + ui.item("News", key="news"), + ui.item("Travel", key="travel"), + ui.item("Gaming", key="gaming"), + ui.item("Shopping", key="shopping"), + ] + ) + + return ui.tag_group( + *items, + action_label="Delete Shopping", + on_action=lambda: set_items([item for item in items if item.key != "shopping"]) + ) + + +my_tag_group_action_example = tag_group_action_example() +``` + +## Links + +Tags can become links to another page or website by passing the `href` prop to the `ui.item` component. The target window to open the link in can be configured using the `target` prop. + +```python +from deephaven import ui + + +tag_group_links_example = ui.tag_group( + ui.item("Adobe", key="adobe", href="https://adobe.com/", target="_blank"), + ui.item("Apple", key="apple", href="https://apple.com/", target="_blank"), + ui.item("Google", key="google", href="https://google.com/", target="_blank"), +) +``` + +## Help text + +A tag group can have both a `description` and an `error_message`. The error message should offer specific guidance on how to correct the input. + +The `is_invalid` prop can be used to set whether the current tag group state is valid or invalid. + +```python +from deephaven import ui + + +@ui.component +def tag_group_help_text_example(): + items, set_items = ui.use_state( + [ + ui.item("News", key="news"), + ui.item("Travel", key="travel"), + ui.item("Gaming", key="gaming"), + ui.item("Shopping", key="shopping"), + ] + ) + + return ( + ui.tag_group( + *items, + on_remove=lambda keys: set_items( + [item for item in items if item.key not in keys] + ), + is_invalid=len(items) > 3, + description="Please include tags for related categories.", + error_message="Must contain no more than 3 tags. Please remove some.", + ), + ) + + +my_tag_group_help_text_example = tag_group_help_text_example() +``` + +## Contextual help + +Using the `contextual_help` prop, a `ui.contextual_help` can be placed next to the label to provide additional information about the tag group. + +```python +from deephaven import ui + + +tag_group_contextual_help_example = ui.tag_group( + ui.item("News", key="news"), + ui.item("Travel", key="travel"), + ui.item("Gaming", key="gaming"), + ui.item("Shopping", key="shopping"), + label="Categories", + contextual_help=ui.contextual_help( + heading="Hint", content="Pick your favorite category" + ), +) +``` + +## Limit rows + +To limit the number of rows initially shown, use the `max_rows` prop. A button to allow the user to expand to show all tags will be displayed if the tags would overflow the number of rows. + +```python +from deephaven import ui + +tag_group_max_rows_example = ui.flex( + ui.view( + ui.tag_group( + ui.item("News", key="news"), + ui.item("Travel", key="travel"), + ui.item("Gaming", key="gaming"), + ui.item("Shopping", key="shopping"), + ), + border_width="thin", + border_color="accent-400", + width="size-2000", + ), + ui.view( + ui.tag_group( + ui.item("News", key="news"), + ui.item("Travel", key="travel"), + ui.item("Gaming", key="gaming"), + ui.item("Shopping", key="shopping"), + max_rows=1, + ), + border_width="thin", + border_color="accent-400", + width="size-2000", + ), + direction="column", +) +``` + +## Empty state + +By default the empty state displays the text "None". + +```python +from deephaven import ui + + +my_tag_group_empty_default = ui.tag_group() +``` + +Use the `render_empty_state` prop to specify the element to be displayed when the tag group will display when no tags are provided. + +```python +from deephaven import ui + + +my_tag_group_empty_custom = ui.tag_group( + render_empty_state=ui.flex( + ui.icon("dh_warning_circle_filled", size="S"), + ui.text("No tags here"), + align_items="center", + ), +) +``` + +## API reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.tag_group +``` diff --git a/plugins/ui/docs/sidebar.json b/plugins/ui/docs/sidebar.json index 113059497..52817e2dd 100644 --- a/plugins/ui/docs/sidebar.json +++ b/plugins/ui/docs/sidebar.json @@ -200,6 +200,10 @@ "label": "inline_alert", "path": "components/inline_alert.md" }, + { + "label": "labeled_value", + "path": "components/labeled_value.md" + }, { "label": "link", "path": "components/link.md" @@ -280,6 +284,10 @@ "label": "tabs", "path": "components/tabs.md" }, + { + "label": "tag_group", + "path": "components/tag_group.md" + }, { "label": "text", "path": "components/text.md" diff --git a/plugins/ui/src/deephaven/ui/components/__init__.py b/plugins/ui/src/deephaven/ui/components/__init__.py index 046b41c94..4d448473b 100644 --- a/plugins/ui/src/deephaven/ui/components/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/__init__.py @@ -34,6 +34,7 @@ from .inline_alert import inline_alert from .item import item from .item_table_source import item_table_source +from .labeled_value import labeled_value from .link import link from .list_action_group import list_action_group from .list_action_menu import list_action_menu @@ -65,6 +66,7 @@ from .tab import tab from .table import table, TableDatabar, TableFormat from .tabs import tabs +from .tag_group import tag_group from .text import text from .text_area import text_area from .text_field import text_field @@ -111,6 +113,7 @@ "item_table_source", "illustrated_message", "image", + "labeled_value", "inline_alert", "link", "list_view", @@ -145,6 +148,7 @@ "tab_panels", "tabs", "tab", + "tag_group", "text", "text_area", "text_field", diff --git a/plugins/ui/src/deephaven/ui/components/checkbox_group.py b/plugins/ui/src/deephaven/ui/components/checkbox_group.py index 073328090..7996f14e4 100644 --- a/plugins/ui/src/deephaven/ui/components/checkbox_group.py +++ b/plugins/ui/src/deephaven/ui/components/checkbox_group.py @@ -4,6 +4,7 @@ from .types import ( Orientation, + Alignment, AlignSelf, CSSProperties, DimensionValue, @@ -12,6 +13,7 @@ Position, ValidationBehavior, FocusEventCallable, + LabelPosition, ) from .basic import component_element from ..elements import Element @@ -33,8 +35,8 @@ def checkbox_group( is_required: bool | None = None, is_invalid: bool | None = None, validation_behavior: ValidationBehavior | None = "aria", - label_position: str | None = None, - label_align: str | None = None, + label_position: LabelPosition | None = None, + label_align: Alignment | None = None, necessity_indicator: str | None = None, contextual_help: Any | None = None, show_error_icon: bool | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/item.py b/plugins/ui/src/deephaven/ui/components/item.py index b12aa953a..87082baea 100644 --- a/plugins/ui/src/deephaven/ui/components/item.py +++ b/plugins/ui/src/deephaven/ui/components/item.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Any, Union, List +from typing import Any, Union, List, Literal from ..elements import BaseElement from ..types import Stringable @@ -16,6 +16,8 @@ def item( title: str | None = None, text_value: str | None = None, aria_label: str | None = None, + href: str | None = None, + target: Literal["_self", "_blank", "_parent", "_top"] | str | None = None, key: str | None = None, **props: Any, ) -> ItemElement: @@ -27,6 +29,8 @@ def item( title: Rendered contents of the item if `children` contains child items. text_value: A string representation of the item's contents, used for features like typeahead. aria_label: An accessibility label for this item. + href: A URL to link to. + target: The target window for the link. key: A unique identifier used by React to render elements in a list. **props: Any other Item prop. """ diff --git a/plugins/ui/src/deephaven/ui/components/labeled_value.py b/plugins/ui/src/deephaven/ui/components/labeled_value.py new file mode 100644 index 000000000..2f3e36f3a --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/labeled_value.py @@ -0,0 +1,166 @@ +from __future__ import annotations +from typing import Any, List + +from .number_field import NumberFormatOptions +from .types import ( + Alignment, + AlignSelf, + CSSProperties, + DimensionValue, + JustifySelf, + LayoutFlex, + Position, + LabelPosition, +) +from .basic import component_element +from ..elements import Element + + +def labeled_value( + value: str | List[str] | int | None = None, + label: Element | None = None, + format_options: NumberFormatOptions | None = None, + label_position: LabelPosition | None = "top", + label_align: Alignment | None = None, + contextual_help: Any | None = None, + flex: LayoutFlex | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, + flex_basis: DimensionValue | None = None, + align_self: AlignSelf | None = None, + justify_self: JustifySelf | None = None, + order: int | None = None, + grid_area: str | None = None, + grid_row: str | None = None, + grid_row_start: str | None = None, + grid_row_end: str | None = None, + grid_column: str | None = None, + grid_column_start: str | None = None, + grid_column_end: str | None = None, + margin: DimensionValue | None = None, + margin_top: DimensionValue | None = None, + margin_bottom: DimensionValue | None = None, + margin_start: DimensionValue | None = None, + margin_end: DimensionValue | None = None, + margin_x: DimensionValue | None = None, + margin_y: DimensionValue | None = None, + width: DimensionValue | None = None, + height: DimensionValue | None = None, + min_width: DimensionValue | None = None, + min_height: DimensionValue | None = None, + max_width: DimensionValue | None = None, + max_height: DimensionValue | None = None, + position: Position | None = None, + top: DimensionValue | None = None, + bottom: DimensionValue | None = None, + start: DimensionValue | None = None, + end: DimensionValue | None = None, + left: DimensionValue | None = None, + right: DimensionValue | None = None, + z_index: int | None = None, + is_hidden: bool | None = None, + id: str | None = None, + UNSAFE_class_name: str | None = None, + UNSAFE_style: CSSProperties | None = None, +) -> Element: + """ + Labeled values displays non-editable values with a corresponding label + + Args: + value: The value to be displayed. + label: The content of the label. + format_options: Formatting options for the value displayed in the number field. + label_position: The label's overall position relative to the element it is labeling. + label_align: The label's horizontal alignment relative to the element it is labeling. + contextual_help: A contextual help element to place next to the label. + flex: When used in a flex layout, specifies how the element will grow or shrink to fit the space available. + flex_grow: When used in a flex layout, specifies how the element will grow to fit the space available. + flex_shrink: When used in a flex layout, specifies how the element will shrink to fit the space available. + flex_basis: When used in a flex layout, specifies the initial main size of the element. + align_self: Overrides the alignItems property of a flex or grid container. + justify_self: Species how the element is justified inside a flex or grid container. + order: The layout order for the element within a flex or grid container. + grid_area: When used in a grid layout specifies, specifies the named grid area that the element should be placed in within the grid. + grid_row: When used in a grid layout, specifies the row the element should be placed in within the grid. + grid_column: When used in a grid layout, specifies the column the element should be placed in within the grid. + grid_row_start: When used in a grid layout, specifies the starting row to span within the grid. + grid_row_end: When used in a grid layout, specifies the ending row to span within the grid. + grid_column_start: When used in a grid layout, specifies the starting column to span within the grid. + grid_column_end: When used in a grid layout, specifies the ending column to span within the grid. + margin: The margin for all four sides of the element. + margin_top: The margin for the top side of the element. + margin_bottom: The margin for the bottom side of the element. + margin_start: The margin for the logical start side of the element, depending on layout direction. + margin_end: The margin for the logical end side of the element, depending on layout direction. + margin_x: The margin for the left and right sides of the element. + margin_y: The margin for the top and bottom sides of the element. + width: The width of the element. + min_width: The minimum width of the element. + max_width: The maximum width of the element. + height: The height of the element. + min_height: The minimum height of the element. + max_height: The maximum height of the element. + position: The position of the element. + top: The distance from the top of the containing element. + bottom: The distance from the bottom of the containing element. + left: The distance from the left of the containing element. + right: The distance from the right of the containing element. + start: The distance from the start of the containing element, depending on layout direction. + end: The distance from the end of the containing element, depending on layout direction. + z_index: The stack order of the element. + is_hidden: Whether the element is hidden. + id: The unique identifier of the element. + UNSAFE_class_name: A CSS class to apply to the element. + UNSAFE_style: A CSS style to apply to the element. + + Returns: + The rendered labeled value element. + """ + return component_element( + "LabeledValue", + value=value, + label=label, + format_options=format_options, + label_position=label_position, + label_align=label_align, + contextual_help=contextual_help, + flex=flex, + flex_grow=flex_grow, + flex_shrink=flex_shrink, + flex_basis=flex_basis, + align_self=align_self, + justify_self=justify_self, + order=order, + grid_area=grid_area, + grid_row=grid_row, + grid_row_start=grid_row_start, + grid_row_end=grid_row_end, + grid_column=grid_column, + grid_column_start=grid_column_start, + grid_column_end=grid_column_end, + margin=margin, + margin_top=margin_top, + margin_bottom=margin_bottom, + margin_start=margin_start, + margin_end=margin_end, + margin_x=margin_x, + margin_y=margin_y, + width=width, + height=height, + min_width=min_width, + min_height=min_height, + max_width=max_width, + max_height=max_height, + position=position, + top=top, + bottom=bottom, + start=start, + end=end, + left=left, + right=right, + z_index=z_index, + is_hidden=is_hidden, + id=id, + UNSAFE_class_name=UNSAFE_class_name, + UNSAFE_style=UNSAFE_style, + ) diff --git a/plugins/ui/src/deephaven/ui/components/tag_group.py b/plugins/ui/src/deephaven/ui/components/tag_group.py new file mode 100644 index 000000000..ae1467e11 --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/tag_group.py @@ -0,0 +1,206 @@ +from __future__ import annotations + +from typing import Callable, List + +from .basic import component_element +from .section import Item +from ..elements import Element, NodeType +from ..types import Key +from .types import ( + LabelPosition, + Alignment, + AlignSelf, + CSSProperties, + DimensionValue, + JustifySelf, + LayoutFlex, + Position, +) + + +def tag_group( + *children: Item, + action_label: str | None = None, + render_empty_state: Element | None = None, + max_rows: int | None = None, + error_message: NodeType = None, + label: NodeType = None, + description: NodeType = None, + label_position: LabelPosition = "top", + label_align: Alignment | None = "start", + contextual_help: NodeType = None, + is_invalid: bool | None = None, + on_action: Callable[[None], None] | None = None, + on_remove: Callable[[List[Key]], None] | None = None, + flex: LayoutFlex | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, + flex_basis: DimensionValue | None = None, + align_self: AlignSelf | None = None, + justify_self: JustifySelf | None = None, + order: int | None = None, + grid_area: str | None = None, + grid_column: str | None = None, + grid_row: str | None = None, + grid_column_start: str | None = None, + grid_column_end: str | None = None, + grid_row_start: str | None = None, + grid_row_end: str | None = None, + margin: DimensionValue | None = None, + margin_top: DimensionValue | None = None, + margin_bottom: DimensionValue | None = None, + margin_start: DimensionValue | None = None, + margin_end: DimensionValue | None = None, + margin_x: DimensionValue | None = None, + margin_y: DimensionValue | None = None, + width: DimensionValue | None = None, + min_width: DimensionValue | None = None, + max_width: DimensionValue | None = None, + height: DimensionValue | None = None, + min_height: DimensionValue | None = None, + max_height: DimensionValue | None = None, + position: Position | None = None, + top: DimensionValue | None = None, + bottom: DimensionValue | None = None, + left: DimensionValue | None = None, + right: DimensionValue | None = None, + start: DimensionValue | None = None, + end: DimensionValue | None = None, + z_index: int | None = None, + is_hidden: bool | None = None, + id: str | None = None, + aria_label: str | None = None, + aria_labelledby: str | None = None, + aria_describedby: str | None = None, + aria_details: str | None = None, + UNSAFE_class_name: str | None = None, + UNSAFE_style: CSSProperties | None = None, + key: str | None = None, +) -> Element: + """ + A tag group displays a list of keywords to describe an item. + + Args: + *children: The tags to render within the tag_group. + action_label: The label for the action button. If provided, an action button will be displayed. + render_empty_state: The element that will be rendered when there is no content to display. + max_rows: The maximum amount of rows to show. If provided, will render a button that allows the user to expand the tag group. + error_message: An error message for the field. + label: The label for the tag group. + description: A description for the tag group. + label_position: The position of the label relative to the input. + label_align: The alignment of the label relative to the input. + contextual_help: A ContextualHelp element to place next to the label. + is_invalid: Whether the tag group is in an invalid state. + on_action: The handler that is called when action button is clicked. + on_remove: The handler that is called when remove button is clicked. If provided, a remove button will be displayed on each tag. + flex: When used in a flex layout, specifies how the element will grow or shrink to fit the space available. + flex_grow: When used in a flex layout, specifies how much the element will grow to fit the space available. + flex_shrink: When used in a flex layout, specifies how much the element will shrink to fit the space available. + flex_basis: When used in a flex layout, specifies the initial size of the element. + align_self: Overrides the align_items property of a flex or grid container. + justify_self: Specifies how the element is justified inside a flex or grid container. + order: The layout order for the element within a flex or grid container. + grid_area: The name of the grid area to place the element in. + grid_row: The name of the grid row to place the element in. + grid_row_start: The name of the grid row to start the element in. + grid_row_end: The name of the grid row to end the element in. + grid_column: The name of the grid column to place the element in. + grid_column_start: The name of the grid column to start the element in. + grid_column_end: The name of the grid column to end the element in. + margin: The margin to apply around the element. + margin_top: The margin to apply above the element. + margin_bottom: The margin to apply below the element. + margin_start: The margin to apply before the element. + margin_end: The margin to apply after the element. + margin_x: The margin to apply to the left and right of the element. + margin_y: The margin to apply to the top and bottom of the element. + width: The width of the element. + height: The height of the element. + min_width: The minimum width of the element. + min_height: The minimum height of the element. + max_width: The maximum width of the element. + max_height: The maximum height of the element. + position: Specifies how the element is positioned. + top: The distance from the top of the containing element. + bottom: The distance from the bottom of the containing element. + start: The distance from the start of the containing element. + end: The distance from the end of the containing element. + left: The distance from the left of the containing element. + right: The distance from the right of the containing element. + z_index: The stack order of the element. + is_hidden: Whether the element is hidden. + id: A unique identifier for the element. + aria_label: The label for the element. + aria_labelledby: The id of the element that labels the element. + aria_describedby: The id of the element that describes the element. + aria_details: The details for the element. + UNSAFE_class_name: A CSS class to apply to the element. + UNSAFE_style: A CSS style to apply to the element. + key: A unique identifier used by React to render elements in a list. + + + Returns: + The rendered tag group element. + """ + + return component_element( + "TagGroup", + *children, + action_label=action_label, + render_empty_state=render_empty_state, + max_rows=max_rows, + error_message=error_message, + label=label, + description=description, + label_position=label_position, + label_align=label_align, + contextual_help=contextual_help, + is_invalid=is_invalid, + on_action=on_action, + on_remove=on_remove, + flex=flex, + flex_grow=flex_grow, + flex_shrink=flex_shrink, + flex_basis=flex_basis, + align_self=align_self, + justify_self=justify_self, + order=order, + grid_area=grid_area, + grid_column=grid_column, + grid_row=grid_row, + grid_column_start=grid_column_start, + grid_column_end=grid_column_end, + grid_row_start=grid_row_start, + grid_row_end=grid_row_end, + margin=margin, + margin_top=margin_top, + margin_bottom=margin_bottom, + margin_start=margin_start, + margin_end=margin_end, + margin_x=margin_x, + margin_y=margin_y, + width=width, + min_width=min_width, + max_width=max_width, + height=height, + min_height=min_height, + max_height=max_height, + position=position, + top=top, + bottom=bottom, + left=left, + right=right, + start=start, + end=end, + z_index=z_index, + is_hidden=is_hidden, + id=id, + aria_label=aria_label, + aria_labelled_by=aria_labelledby, + aria_described_by=aria_describedby, + aria_details=aria_details, + UNSAFE_class_name=UNSAFE_class_name, + UNSAFE_style=UNSAFE_style, + key=key, + ) diff --git a/plugins/ui/src/js/src/elements/LabeledValue.tsx b/plugins/ui/src/js/src/elements/LabeledValue.tsx new file mode 100644 index 000000000..dadf6920e --- /dev/null +++ b/plugins/ui/src/js/src/elements/LabeledValue.tsx @@ -0,0 +1,15 @@ +import { + LabeledValue as DHCLabeledValue, + LabeledValueProps as DHCLabeledValueProps, +} from '@deephaven/components'; + +export function LabeledValue( + props: DHCLabeledValueProps +): JSX.Element { + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ); +} +LabeledValue.displayName = 'LabeledValue'; +export default LabeledValue; diff --git a/plugins/ui/src/js/src/elements/TagGroup.tsx b/plugins/ui/src/js/src/elements/TagGroup.tsx new file mode 100644 index 000000000..1d0dc07c9 --- /dev/null +++ b/plugins/ui/src/js/src/elements/TagGroup.tsx @@ -0,0 +1,31 @@ +import { + TagGroup as DHCTagGroup, + TagGroupProps as DHCTagGroupProps, +} from '@deephaven/components'; +import { useConditionalCallback } from './hooks'; + +export function TagGroup( + props: Omit, 'onRemove' | 'renderEmptyState'> & { + onRemove?: (keys: React.Key[]) => void; + renderEmptyState?: JSX.Element; + } +): JSX.Element { + const { onRemove: propOnRemove, renderEmptyState, ...otherProps } = props; + + const onRemove = useConditionalCallback( + propOnRemove != null, + (keys: Set) => propOnRemove?.(Array.from(keys)), + [propOnRemove] + ); + + return ( + renderEmptyState : undefined} + /* eslint-disable-next-line react/jsx-props-no-spreading */ + {...otherProps} + /> + ); +} + +export default TagGroup; diff --git a/plugins/ui/src/js/src/elements/index.ts b/plugins/ui/src/js/src/elements/index.ts index 79fb7f7ad..bcf54f718 100644 --- a/plugins/ui/src/js/src/elements/index.ts +++ b/plugins/ui/src/js/src/elements/index.ts @@ -17,6 +17,7 @@ export * from './HTMLElementView'; export * from './IconElementView'; export * from './IllustratedMessage'; export * from './Image'; +export * from './LabeledValue'; export * from './InlineAlert'; export * from './ListView'; export * from './LogicButton'; @@ -36,6 +37,7 @@ export * from './SearchField'; export * from './Slider'; export * from './Tabs'; export * from './TabPanels'; +export * from './TagGroup'; export * from './TextField'; export * from './TextArea'; export * from './TimeField'; diff --git a/plugins/ui/src/js/src/elements/model/ElementConstants.ts b/plugins/ui/src/js/src/elements/model/ElementConstants.ts index be24bd42b..150a30be8 100644 --- a/plugins/ui/src/js/src/elements/model/ElementConstants.ts +++ b/plugins/ui/src/js/src/elements/model/ElementConstants.ts @@ -52,6 +52,7 @@ export const ELEMENT_NAME = { image: uiComponentName('Image'), inlineAlert: uiComponentName('InlineAlert'), item: uiComponentName('Item'), + labeledValue: uiComponentName('LabeledValue'), listActionGroup: uiComponentName('ListActionGroup'), listActionMenu: uiComponentName('ListActionMenu'), link: uiComponentName('Link'), @@ -78,6 +79,7 @@ export const ELEMENT_NAME = { tabPanels: uiComponentName('TabPanels'), tabs: uiComponentName('Tabs'), tab: uiComponentName('Tab'), + tagGroup: uiComponentName('TagGroup'), text: uiComponentName('Text'), textArea: uiComponentName('TextArea'), textField: uiComponentName('TextField'), diff --git a/plugins/ui/src/js/src/widget/WidgetUtils.tsx b/plugins/ui/src/js/src/widget/WidgetUtils.tsx index 91ba8ca68..cacfd433b 100644 --- a/plugins/ui/src/js/src/widget/WidgetUtils.tsx +++ b/plugins/ui/src/js/src/widget/WidgetUtils.tsx @@ -68,6 +68,7 @@ import { Grid, IllustratedMessage, Image, + LabeledValue, InlineAlert, ListView, LogicButton, @@ -84,6 +85,7 @@ import { SearchField, Slider, TabPanels, + TagGroup, TextField, TextArea, TimeField, @@ -151,6 +153,7 @@ export const elementComponentMap: Record, unknown> = { [ELEMENT_NAME.image]: Image, [ELEMENT_NAME.inlineAlert]: InlineAlert, [ELEMENT_NAME.item]: Item, + [ELEMENT_NAME.labeledValue]: LabeledValue, [ELEMENT_NAME.link]: Link, [ELEMENT_NAME.listActionGroup]: ListActionGroup, [ELEMENT_NAME.listActionMenu]: ListActionMenu, @@ -177,6 +180,7 @@ export const elementComponentMap: Record, unknown> = { [ELEMENT_NAME.tabPanels]: TabPanels, [ELEMENT_NAME.tab]: Item, [ELEMENT_NAME.tabs]: Tabs, + [ELEMENT_NAME.tagGroup]: TagGroup, [ELEMENT_NAME.text]: Text, [ELEMENT_NAME.textArea]: TextArea, [ELEMENT_NAME.textField]: TextField, diff --git a/tests/app.d/ui_render_all.py b/tests/app.d/ui_render_all.py index 36bf2ee3c..fad976cd3 100644 --- a/tests/app.d/ui_render_all.py +++ b/tests/app.d/ui_render_all.py @@ -68,13 +68,13 @@ def ui_components1(): ui.form("Form"), ui.fragment("Fragment"), ui.grid("Grid A", "Grid B"), - ui.heading("Heading"), ) @ui.component def ui_components2(): return ( + ui.heading("Heading"), ui.icon("vsSymbolMisc"), ui.illustrated_message( ui.icon("vsWarning"), @@ -88,6 +88,7 @@ def ui_components2(): ), variant="positive", ), + ui.labeled_value(label="File name", value="Budget.xls"), ui.link("Learn more about Deephaven", href="https://deephaven.io/"), ui.list_view( _item_table_source_with_action_group, @@ -118,6 +119,12 @@ def ui_components2(): ui.range_calendar( default_value={"start": "2021-01-01", "end": "2021-01-02"}, ), + ) + + +@ui.component +def ui_components3(): + return ( ui.range_slider(default_value={"start": 10, "end": 99}, label="Range Slider"), ui.row("Row child A", "Row child B"), ui.slider( @@ -132,6 +139,11 @@ def ui_components2(): # ui.tab_list("Tab List"), # ui.tab_panels("Tab Panels"), # ui.tabs("Tabs"), + ui.tag_group( + ui.item("Tag 1", key="1"), + ui.item("Tag 2", key="2"), + ui.item("Tag 3", key="3"), + ), ui.text("Text"), ui.text_field( ui.icon("vsSymbolMisc"), default_value="Text Field", label="Text Field" @@ -162,6 +174,7 @@ def ui_html_elements(): _my_components1 = ui_components1() _my_components2 = ui_components2() +_my_components3 = ui_components3() _my_html_elements = ui_html_elements() ui_render_all1 = ui.dashboard( @@ -191,3 +204,16 @@ def ui_html_elements(): ), ) ) + +ui_render_all3 = ui.dashboard( + ui.stack( + ui.panel( + ui.grid( + _my_components3, + columns=["1fr", "1fr", "1fr"], + width="100%", + ), + title="Panel D", + ), + ) +) diff --git a/tests/ui.spec.ts b/tests/ui.spec.ts index 7e182bc11..975b11940 100644 --- a/tests/ui.spec.ts +++ b/tests/ui.spec.ts @@ -74,6 +74,12 @@ test('UI all components render 2', async ({ page }) => { await expect(page.locator(SELECTORS.REACT_PANEL_VISIBLE)).toHaveScreenshot(); }); +test('UI all components render 3', async ({ page }) => { + await gotoPage(page, ''); + await openPanel(page, 'ui_render_all3', SELECTORS.REACT_PANEL_VISIBLE); + await expect(page.locator(SELECTORS.REACT_PANEL_VISIBLE)).toHaveScreenshot(); +}); + // Tests flex components render as expected test.describe('UI flex components', () => { [ diff --git a/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-chromium-linux.png b/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-chromium-linux.png index e6d46057b..062323e81 100644 Binary files a/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-chromium-linux.png and b/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-chromium-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-firefox-linux.png b/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-firefox-linux.png index 594d143e4..cb94d7c6e 100644 Binary files a/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-firefox-linux.png and b/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-firefox-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-webkit-linux.png b/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-webkit-linux.png index e1d3c4e54..f4831c2ba 100644 Binary files a/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-webkit-linux.png and b/tests/ui.spec.ts-snapshots/UI-all-components-render-2-1-webkit-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-all-components-render-3-1-chromium-linux.png b/tests/ui.spec.ts-snapshots/UI-all-components-render-3-1-chromium-linux.png new file mode 100644 index 000000000..c10c88a13 Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-all-components-render-3-1-chromium-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-all-components-render-3-1-firefox-linux.png b/tests/ui.spec.ts-snapshots/UI-all-components-render-3-1-firefox-linux.png new file mode 100644 index 000000000..dc4ebf0f6 Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-all-components-render-3-1-firefox-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-all-components-render-3-1-webkit-linux.png b/tests/ui.spec.ts-snapshots/UI-all-components-render-3-1-webkit-linux.png new file mode 100644 index 000000000..5ad149bc1 Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-all-components-render-3-1-webkit-linux.png differ