diff --git a/plugins/ui/DESIGN.md b/plugins/ui/DESIGN.md index 75ea40de7..dfa79ec6d 100644 --- a/plugins/ui/DESIGN.md +++ b/plugins/ui/DESIGN.md @@ -2676,6 +2676,71 @@ ui_table.sort( | `by` | `str \| Sequence[str]` | The column(s) to sort by. May be a single column name, or a list of column names. | | `direction` | `TableSortDirection \| Sequence[TableSortDirection] \| None` | The sort direction(s) to use. If provided, that must match up with the columns provided. Defaults to "ASC". | +##### ui.table Formatting Rules + +Tables can be formatted via the `formatting` prop and a Dataclass that contains the different formatting options. We choose to use Dataclass as effectively a more strict dictionary. This allows for better type checking and easier to understand code. + +Formatting can be a variety of different options including background color, font, font color, font size, text alignment, cell rendering mode (data bars, button, etc.), and number formatting. It can be thought of as any visual manipulation of the base table display and should be flexible enough to allow for any kind of formatting that the user desires (assuming the rule is supported). + +The formatting rules should have 3 main properties: + +1. The formatting to apply. +2. Where to apply the formatting. +3. An optional conditional expression that will determine if the formatting should be applied (the `where` arg). + +Outside of these 3 properties, it is up to the Dataclass to identify what kind of formatting it is and up to the web to apply that formatting. + +On the client, formatting rules should be applied in the order they are defined in the list and stop as soon as a rule is applied. This should only prevent later rules from being applied if they are the same type of rule. For example, if a color rule is applied to a cell, a number formatting rule should still be applied to that cell if it is defined later in the list, but another color rule should not be applied. + +We will use a single dataclass that encompasses both row and column formatting. If `cols` is provided, it is a column rule, else it is a row rule. This dataclass will support the different formatting types as a keyword argument. This allows for multiple different formatting rules to be applied to a column or row with the same condition without repeating. + +In the future we may implement column and row formatting dataclasses which would extend the main formatting class and require or prohibit `cols`. + +We should support some method of applying value formatting to all columns that support it. This would be useful for number formatting, date formatting, etc. We could support `cols="*"` or if there is no `cols` (indicating a row rule), we could apply the value formatting to all columns that are supported in the matching rows (or all rows if no `where`). + +We could also support regex (or just wildcards) in the `cols` field to apply the rule to multiple columns at once. This could be useful if a user wanted to format all columns that end in percent as a percentage. Something like `FORMAT(cols="*Percent", format="0.00%")`. We could require regex strings be surrounded by `/` to differentiate them from normal strings with wildcards. + +```py +from deephaven import ui, time_table + +_t = time_table("PT1S").update("X=i % 10", "Y=i % 10", "Z=i % 100") + +t = ui.table( + t, + formatting=[ + ui.table.FORMAT(cols="X", color="RED"), + ui.table.FORMAT(cols="Y", color="BLUE", where="Y % 2 == 0"), + ui.table.FORMAT(cols="Y", value="0.00"), + ui.table.FORMAT(cols=["A", "B"], color="PURPLE", value="0.00%", where="A > 5"), + ui.table.FORMAT(cols="Z", mode=ui.table.DATABAR(value_col="Z", min=0, max=100, positive_color="GREEN", negative_color="RED"), + ui.table.FORMAT(where="X > 5", color="GREEN") + ] +) +``` + +##### ui.table Formatting Rule Types + +There are 3 main types of formatting rules: those which affect basic visual aspects (color, font, etc.), those which affect the display value (number formatting, etc.), and those which affect the cell rendering mode (data bars, buttons, etc.). Multiple different visual rules can be applied at the same time, but only one display value rule and one cell rendering mode rule can be applied at a time. It doesn't make sense to have a cell that is both a data bar and a button, for example. If a rule is applied conditionally, multiple display or cell rendering rules should be allowed to be applied in a column. + +Some examples of potential rules that fall into each category: + +1. Visual (each should have a keyword arg in the dataclass) + - Background Color + - Font + - Font Color + - Font Size + - Text Alignment +2. Display Value (one keyword arg such as `display` in the dataclass) + - Number Formatting + - Date Formatting + - DateTime Formatting + - String Formatting +3. Cell Rendering Mode (one keyword arg such as `mode` in the dataclass) + - Data Bars + - Buttons + - Checkboxes + - Icons + ###### ui.time_field A time field that can be used to select a time. diff --git a/plugins/ui/docs/components/checkbox_group.md b/plugins/ui/docs/components/checkbox_group.md new file mode 100644 index 000000000..aca3cdda9 --- /dev/null +++ b/plugins/ui/docs/components/checkbox_group.md @@ -0,0 +1,357 @@ +# Checkbox Group + +Checkbox group areas allow the selection of one or more items from a list of choices, represented by checkboxes. + +## Example + +```python +from deephaven import ui + + +my_checkbox_group_basic = ui.checkbox_group( + "Soccer", + "Basketball", + "Baseball", + label="Favourite Sports", +) +``` + +## UI Recommendations + +Recommendations for creating checkbox groups: + +1. Every checkbox group should have a [label](#labeling) specified. Without one, the checkbox group is ambiguous. In the rare case that context is sufficient, the label is unnecessary; you must still include an aria-label via the `aria_label` prop. +2. While labels can be placed either on top or on the side of the checkbox groups, top labels are the default recommendation. Top labels work better with longer copy, localization, and responsive layouts. Side labels are more useful when vertical space is limited. +3. Checkbox groups can be either horizontal or vertical. By default, they are vertical; the orientation should only be horizontal if vertical space is limited. +4. Checkbox groups can be marked as optional or required, with required groups indicated by either a “(required)” label or an asterisk icon, which should be accompanied by help text. +5. Checkbox groups should use help text for error messaging and descriptions, providing context for why a selection is required or clarifying the options. + + +Consider using [`checkbox_group`](./checkbox_group.md) to select or mark a single item as selected. + +## Content + +Checkbox groups accept checkboxes and primitive types as children. Checkboxes accept a child, which is rendered as the checkbox's label. + +```python +from deephaven import ui + + +my_checkbox_group_content_example = ui.checkbox_group( + "Soccer", + ui.checkbox("Basketball"), + label="Favourite Sports", +) +``` + + +## Value + +Checkbox groups allow selecting zero or more items, with initial values set via `default_value` or controlled values via `value`. + +```python +from deephaven import ui + + +@ui.component +def ui_checkbox_group_value_examples(): + value, set_value = ui.use_state(["Soccer"]) + return [ + ui.checkbox_group( + "Soccer", + "Basketball", + "Baseball", + label="Favourite Sports (uncontrolled)", + default_value=["Soccer", "Baseball"], + ), + ui.checkbox_group( + "Soccer", + "Basketball", + "Baseball", + label="Favourite Sports (controlled)", + value=value, + on_change=set_value, + ), + ] + + +my_checkbox_group_value_examples = ui_checkbox_group_value_examples() +``` + + +## HTML Forms + +Checkbox groups can support a `name` prop for integration with HTML forms, allowing for easy identification of a value on form submission. + +```python +from deephaven import ui + + +my_checkbox_name_example = ui.form( + ui.checkbox_group(ui.checkbox("Sample Label"), name="Sample Name") +) +``` + + +## Labeling + +The checkbox group can be labeled using the `label` prop, and if no label is provided, an `aria_label` must be provided to identify the control for accessibility purposes. + +```python +from deephaven import ui + + +@ui.component +def ui_checkbox_group_label_examples(): + return [ + ui.checkbox_group( + ui.checkbox("Wizard", value="wizard"), + ui.checkbox("Dragon", value="dragon"), + label="Favorite avatars", + ), + ui.checkbox_group( + ui.checkbox("Wizard", value="wizard"), + ui.checkbox("Dragon", value="dragon"), + aria_label="Favorite avatars", + ), + ] + + +my_checkbox_group_label_examples = ui_checkbox_group_label_examples() +``` + + +The `is_required` prop and the `necessity_indicator` props can be used to show whether selecting an option in the checked group is required or optional. + +When the `necessity_indicator` prop is set to "label", a localized string will be generated for "(required)" or "(optional)" automatically. + +```python +from deephaven import ui + + +@ui.component +def ui_checkbox_group_required_examples(): + return [ + ui.checkbox_group( + ui.checkbox("Wizard", value="wizard"), + ui.checkbox("Dragon", value="dragon"), + label="Favorite avatars", + is_required=True, + ), + ui.checkbox_group( + ui.checkbox("Wizard", value="wizard"), + ui.checkbox("Dragon", value="dragon"), + label="Favorite avatars", + is_required=True, + necessity_indicator="label", + ), + ui.checkbox_group( + ui.checkbox("Wizard", value="wizard"), + ui.checkbox("Dragon", value="dragon"), + label="Favorite avatars", + necessity_indicator="label", + ), + ] + + +my_checkbox_group_required_examples = ui_checkbox_group_required_examples() +``` + +## Events + +Checkbox groups accept an `on_change` prop, triggered whenever a checkbox within the group is clicked. + +```python +from deephaven import ui + + +@ui.component +def ui_checkbox_group_event_example(): + selected, set_selected = ui.use_state(["Soccer"]) + return [ + ui.checkbox_group( + "Soccer", + "Basketball", + "Baseball", + label="Favourite Sports (controlled)", + value=selected, + on_change=set_selected, + ), + f"You have selected: {selected}!", + ] + + +my_checkbox_group_event_example = ui_checkbox_group_event_example() +``` + +To require specific checkboxes to be checked, set the `is_required` prop at the Checkbox level, not the Checkbox Group. + +```python +from deephaven import ui + + +my_checkbox_group_individual_validation_example = ui.form( + ui.checkbox_group( + ui.checkbox("Terms and conditions", value="terms", is_required=True), + ui.checkbox("Privacy policy", value="privacy", is_required=True), + label="Agree to the following", + is_required=True, + ), + ui.button_group( + ui.button("Submit", type="submit", variant="primary"), + ui.button("Reset", type="reset", variant="secondary"), + ), + on_submit=lambda: print("Form submitted!"), + validation_behavior="native", +) +``` + + +## Orientation + +While aligned vertically by default, the axis with which the checkboxes align can be changed via the `orientation` prop. + +```python +from deephaven import ui + + +my_checkbox_group_orientation_example = ui.checkbox_group( + ui.checkbox("Wizard", value="wizard"), + ui.checkbox("Dragon", value="dragon"), + label="Favorite avatars", + orientation="horizontal", +) +``` + +## Label position + +By default, the position of a checkbox group's label is above the checkbox group, but it can be changed to the side using the `label_position` prop. + +```python +from deephaven import ui + + +my_checkbox_group_label_position_example = ui.checkbox_group( + ui.checkbox("Wizard", value="wizard"), + ui.checkbox("Dragon", value="dragon"), + label="Favorite avatars", + label_position="side", +) +``` + + +## Help text + +A checkbox 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 checkbox group state is valid or invalid. + +```python +from deephaven import ui + + +@ui.component +def ui_checkbox_group_help_text_examples(): + return [ + ui.checkbox_group( + "Soccer", + "Basketball", + "Baseball", + label="Favourite sports", + description="Select an avatar from the two options.", + ), + ui.checkbox_group( + "Soccer", + "Basketball", + "Baseball", + label="Favourite sports", + description="Select favourite sports from the two options.", + error_message="Sample invalid error message.", + is_invalid=True, + ), + ] + + +my_checkbox_group_help_text_examples = ui_checkbox_group_help_text_examples() +``` + + +## Contextual Help + +Using the `contextual_help` prop, a `ui.contextual_help` can be placed next to the label to provide additional information about the checkbox group. + +```python +from deephaven import ui + + +my_checkbox_group_contextual_help_example = ui.checkbox_group( + "Soccer", + "Basketball", + "Baseball", + label="Favorite sports", + contextual_help=ui.contextual_help(ui.heading("Content tips")), +) +``` + + +## Disabled state + +The `is_disabled` prop disables a checkbox group to prevent user interaction. This is useful when the checkbox group should be visible but not available for selection. + +```python +from deephaven import ui + + +my_checkbox_group_is_disabled_example = ui.checkbox_group( + "Soccer", + "Basketball", + "Baseball", + label="Favorite sports", + is_disabled=True, +) +``` + + +## Read only + +The `is_read_only` prop makes checkbox groups read-only to prevent user interaction. This is different from setting the `is_disabled` prop since the checkbox group remains focusable and its options remain visible. + +```python +from deephaven import ui + + +my_checkbox_group_is_read_only_example = ui.checkbox_group( + "Soccer", + "Basketball", + "Baseball", + label="Favorite sports", + is_read_only=True, +) +``` + +## Emphasized + +The `is_emphasized` prop makes the selected checkbox the user's accent color, adding a visual prominence to the selection. + + +```python +from deephaven import ui + + +my_checkbox_group_is_emphasized_example = ui.checkbox_group( + "Soccer", + "Basketball", + "Baseball", + label="Favorite sports", + is_emphasized=True, +) +``` + + + +## API Reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.checkbox_group +``` + diff --git a/plugins/ui/docs/components/list_view.md b/plugins/ui/docs/components/list_view.md new file mode 100644 index 000000000..c66ddc668 --- /dev/null +++ b/plugins/ui/docs/components/list_view.md @@ -0,0 +1,313 @@ +# List View + +List view displays a list of interactive items, and allows a user to navigate, select, or perform an action. It offers greater flexibility in the contents it can render and can distinguish between row selection and actions performed on a row. This makes list view an ideal component for turning table columns into interactive lists. + +## Example + +```python +from deephaven import ui + + +@ui.component +def ui_list_view(): + return ui.list_view( + ui.item("Option 1"), + ui.item("Option 2"), + ui.item("Option 3"), + ui.item("Option 4"), + default_selected_keys=["Option 2", "Option 3"], + ) + + +my_list_view = ui_list_view() +``` + +## Table Source Example + +List view items can also be generated from a table directly or using `item_table_source`. + +### Passing Table Directly + +This method is ideal for quickly displaying a static dataset. By default, the first column is used as the key and label. + +```python +from deephaven import ui, new_table +from deephaven.column import string_col + +_colors = new_table( + [ + string_col("Colors", ["Red", "Blue", "Green"]), + ] +) + + +@ui.component +def ui_list_view_table(): + return ui.list_view(_colors) + + +my_list_view_table = ui_list_view_table() +``` + +### Using item_table_source + +`item_table_source` is used to create complex items from a table (ie., defining which columns are the keys/labels of the data). + +```python +from deephaven import ui, new_table +from deephaven.column import string_col + +_table = new_table( + [ + string_col("Keys", ["key-0", "key-1", "key-2"]), + string_col("Labels", ["Option 0", "Option 1", "Option 2"]), + ] +) + + +@ui.component +def ui_list_view_table_source(): + source = ui.item_table_source(_table, key_column="Keys", label_column="Labels") + return ui.list_view(source) + + +my_list_view_table_source = ui_list_view_table_source() +``` + +## Events + +List view accepts an action that can be triggered when a user performs an action on an item. + +```python +from deephaven import ui, new_table +from deephaven.column import string_col + +_table = new_table( + [ + string_col("Keys", ["key-0", "key-1", "key-2"]), + string_col("Labels", ["Option 0", "Option 1", "Option 2"]), + ] +) + + +@ui.component +def ui_list_view_actions(): + action_item_keys, set_action_item_keys = ui.use_state(["", ""]) + on_action = ui.use_callback( + lambda action_key, item_key: set_action_item_keys([action_key, str(item_key)]), + [], + ) + + source = ui.item_table_source( + _table, + key_column="Keys", + label_column="Labels", + actions=ui.list_action_group( + "Edit", + "Delete", + on_action=on_action, + ), + ) + lv = ui.list_view(source) + + text_action = ui.text("Action: " + " ".join(map(str, action_item_keys))) + + return lv, text_action + + +my_list_view_actions = ui_list_view_actions() +``` + +List view can also accept a handler that is called when the selection is changed. + +```python +from deephaven import ui, new_table +from deephaven.column import string_col + +_table = new_table( + [ + string_col("Keys", ["key-0", "key-1", "key-2"]), + string_col("Labels", ["Option 0", "Option 1", "Option 2"]), + ] +) + + +@ui.component +def ui_list_view_selection(): + value, set_value = ui.use_state(["key-2"]) + + def handle_change(e): + set_value(e) + print("Selection: " + ", ".join(map(str, e))) + + source = ui.item_table_source( + _table, + key_column="Keys", + label_column="Labels", + ) + lv = ui.list_view(source, on_change=handle_change) + + return lv + + +my_list_view_selection = ui_list_view_selection() +``` + +## Disabled Options + +To disable certain rows in the `ListView` component, use the `disabled_keys` prop. By setting this prop with an array of keys, you can prevent interaction with those rows, providing greater control and customization options for the `ListView` behavior. + +```python +from deephaven import ui, new_table +from deephaven.column import string_col + +_table = new_table( + [ + string_col("Keys", ["key-0", "key-1", "key-2"]), + string_col("Labels", ["Option 0", "Option 1", "Option 2"]), + ] +) + + +@ui.component +def ui_list_view_disabled(): + value, set_value = ui.use_state(["key-2"]) + + source = ui.item_table_source( + _table, + key_column="Keys", + label_column="Labels", + ) + lv = ui.list_view( + source, selected_keys=value, on_change=set_value, disabled_keys=["key-0"] + ) + + return lv + + +my_list_view_disabled = ui_list_view_disabled() +``` + +## Quiet State + +```python +from deephaven import ui + + +@ui.component +def ui_list_view_quiet(): + value, set_value = ui.use_state(["Text 2"]) + + quiet_list = ui.list_view( + "Text 1", + "Text 2", + "Text 3", + aria_label="List View - Quiet", + on_change=set_value, + selected_keys=value, + is_quiet=True, + ) + + default_list = ui.list_view( + "Text 1", + "Text 2", + "Text 3", + aria_label="List View - Default", + on_change=set_value, + selected_keys=value, + ) + return quiet_list, default_list + + +my_list_view_quiet = ui_list_view_quiet() +``` + +## Modifying Density + +To adjust the vertical padding of each row in the list view, use the `density` prop. + +```python +from deephaven import ui + + +@ui.component +def ui_list_view_density(): + value, set_value = ui.use_state(["Text 2"]) + + compact_list = ui.list_view( + "Text 1", + "Text 2", + "Text 3", + aria_label="List View - Compact", + on_change=set_value, + selected_keys=value, + density="compact", + ) + + spacious_list = ui.list_view( + "Text 1", + "Text 2", + "Text 3", + aria_label="List View - Spacious", + on_change=set_value, + selected_keys=value, + density="spacious", + ) + return compact_list, spacious_list + + +my_list_view_density = ui_list_view_density() +``` + +## Overflow Mode +The default behavior is to truncate content that overflows its row. Text can be wrapped instead by adding `wrap` to the `overflow_mode` prop. + +Note: Currently not supported if a table source is used. + +```python +from deephaven import ui + + +@ui.component +def ui_list_view_overflow(): + value, set_value = ui.use_state(["Text 2"]) + + truncated_list = ui.list_view( + "Really long Text 1", + "Really long Text 2", + "Really long Text 3", + aria_label="List View - Quiet", + on_change=set_value, + selected_keys=value, + overflow_mode="truncate", + width="150px", + ) + + wrapped_list = ui.list_view( + "Really long Text 1", + "Really long Text 2", + "Really long Text 3", + aria_label="List View - Quiet", + on_change=set_value, + selected_keys=value, + overflow_mode="wrap", + width="150px", + ) + return truncated_list, wrapped_list + + +my_list_view_overflow = ui_list_view_overflow() +``` + + +## API reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.list_view +``` + +## Item Table Source API reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.item_table_source +``` \ No newline at end of file diff --git a/plugins/ui/docs/components/toggle_button.md b/plugins/ui/docs/components/toggle_button.md new file mode 100644 index 000000000..c41756c58 --- /dev/null +++ b/plugins/ui/docs/components/toggle_button.md @@ -0,0 +1,164 @@ +# Toggle Button + +A toggle button allows users to toggle a selection on or off, providing a way to switch between two states, such as enabling or disabling a feature. + +## Example + +```python +from deephaven import ui + +my_toggle_button_basic = ui.toggle_button("Pin") +``` + +## UI Recommendations + +If you want to represent a binary choice for the user, consider using a [`checkbox`](./checkbox.md). + + +## Content + +A toggle button accepts a label, an icon, or both as children. + +```python +from deephaven import ui + + +my_toggle_button = ui.toggle_button(ui.icon("pin"), ui.text("Pin content")) +``` + + +## Accessibility + +If no text is passed into the toggle button, and hence, it has no visible label, the `aria_label` prop should be set for accessibility. + +```python +from deephaven import ui + + +my_toggle_button_accessibility_example = ui.toggle_button( + ui.icon("pin"), aria_label="pin content" +) +``` + + +## Value + +A toggle button is not selected by default. Use the `default_selected` prop to set the initial state (uncontrolled) or the `is_selected` prop to control the selected state. + +```python +from deephaven import ui + + +@ui.component +def ui_toggle_button_value_examples(): + selected, set_selected = ui.use_state(False) + return [ + ui.text("Toggle Button (uncontrolled)"), + ui.toggle_button("Pin", default_selected=True, width="90px"), + ui.text("Toggle Button (controlled)"), + ui.toggle_button( + "Pin", is_selected=selected, on_change=set_selected, width="90px" + ), + ] + + +my_toggle_button_value_examples = ui_toggle_button_value_examples() +``` + + +## Events + +The `on_change` property is triggered whenever the value in the toggle button group selection is changed. + +```python +from deephaven import ui + + +@ui.component +def ui_toggle_button_on_change_example(): + is_selected, set_is_selected = ui.use_state(False) + return [ + ui.toggle_button( + "Pin", + is_selected=is_selected, + on_change=set_is_selected, + ), + ui.text( + f"The toggle button is: `{'selected' if is_selected else 'not selected'}`" + ), + ] + + +my_toggle_button_on_change_example = ui_toggle_button_on_change_example() +``` + + +## Quiet state + +The `is_quiet` prop makes a toggle button "quiet". This can be useful when the toggle button and its corresponding styling should not distract users from surrounding content. + +```python +from deephaven import ui + + +my_toggle_button_is_quiet_example = ui.toggle_button( + "Pin", + is_quiet=True, +) +``` + + +## Disabled state + +The `is_disabled` prop disables a toggle button to prevent user interaction. This is useful when the toggle button should be visible but not available for selection. + +```python +from deephaven import ui + + +my_toggle_button_is_disabled_example = ui.toggle_button( + "Pin", + is_disabled=True, +) +``` + + +## Emphasized + +The `is_emphasized` prop makes the toggle button the user's accent color when selected, adding a visual prominence to the selection. + +```python +from deephaven import ui + + +my_toggle_button_is_emphasized_example = ui.toggle_button( + "Pin", + is_emphasized=True, +) +``` + + +## Static Color + +The `static_color` prop can be used when the toggle button is placed over a colored background. + +```python +from deephaven import ui + + +my_toggle_button_static_color_example = ui.view( + ui.toggle_button( + ui.icon("pin"), + ui.text("Pin content"), + static_color="white", + ), + background_color="blue-700", + padding="size-500", +) +``` + +## API Reference + +```{eval-rst} +.. dhautofunction:: deephaven.ui.toggle_button +``` \ No newline at end of file diff --git a/plugins/ui/docs/sidebar.json b/plugins/ui/docs/sidebar.json index 1fa9dec41..970429771 100644 --- a/plugins/ui/docs/sidebar.json +++ b/plugins/ui/docs/sidebar.json @@ -105,6 +105,10 @@ "label": "text_area", "path": "components/text_area.md" }, + { + "label": "toggle_button", + "path": "components/toggle_button.md" + }, { "label": "view", "path": "components/view.md" diff --git a/plugins/ui/src/deephaven/ui/components/__init__.py b/plugins/ui/src/deephaven/ui/components/__init__.py index 60e7ff751..1d35971a2 100644 --- a/plugins/ui/src/deephaven/ui/components/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/__init__.py @@ -8,6 +8,7 @@ from .button_group import button_group from .calendar import calendar from .checkbox import checkbox +from .checkbox_group import checkbox_group from .column import column from .combo_box import combo_box from .content import content @@ -68,6 +69,7 @@ "button_group", "calendar", "checkbox", + "checkbox_group", "column", "combo_box", "component", diff --git a/plugins/ui/src/deephaven/ui/components/action_group.py b/plugins/ui/src/deephaven/ui/components/action_group.py index 04c6d01f4..715d57eca 100644 --- a/plugins/ui/src/deephaven/ui/components/action_group.py +++ b/plugins/ui/src/deephaven/ui/components/action_group.py @@ -89,7 +89,7 @@ def action_group( """ An action grouping of action items that are related to each other. Args: - *children: The children of the contextual help popover. + *children: The children of the action group. is_emphasized: Whether the action buttons should be displayed with emphasized style. density: Sets the amount of space between buttons. is_justified: Whether the ActionButtons should be justified in their container. diff --git a/plugins/ui/src/deephaven/ui/components/checkbox_group.py b/plugins/ui/src/deephaven/ui/components/checkbox_group.py new file mode 100644 index 000000000..073328090 --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/checkbox_group.py @@ -0,0 +1,238 @@ +from __future__ import annotations +from typing import Any, Callable + + +from .types import ( + Orientation, + AlignSelf, + CSSProperties, + DimensionValue, + JustifySelf, + LayoutFlex, + Position, + ValidationBehavior, + FocusEventCallable, +) +from .basic import component_element +from ..elements import Element +from ..types import Key, Selection + + +def checkbox_group( + *children: Any, + orientation: Orientation = "vertical", + is_emphasized: bool | None = None, + value: Selection | None = None, + default_value: Selection | None = None, + is_disabled: bool | None = None, + is_read_only: bool | None = None, + name: str | None = None, + label: Any | None = None, + description: Any | None = None, + error_message: Any | None = None, + 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, + necessity_indicator: str | None = None, + contextual_help: Any | None = None, + show_error_icon: bool | None = None, + on_change: Callable[[Key], None] | None = None, + on_focus: FocusEventCallable | None = None, + on_blur: FocusEventCallable | None = None, + on_focus_change: Callable[[bool], 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_row: str | None = None, + grid_column: 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, + 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, + 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, + aria_errormessage: str | None = None, + UNSAFE_class_name: str | None = None, + UNSAFE_style: CSSProperties | None = None, +) -> Element: + """ + A grouping of checkbox's that are related to each other. + + Args: + + *children: The children of the checkbox group. + orientation: The axis the CheckboxGroup should align with. + is_emphasized: Whether the checkbox's should be displayed with emphasized style. + value: The selected checkbox within the checkbox group (controlled). + default_value: The default selected checkbox within the checkbox group (uncontrolled). + is_disabled: Whether the checkbox group is disabled. + is_read_only: Whether the checkbox group is read only. + name: The name of the input element, used when submitting an HTML form. + label: The label of the checkbox group. + description: A description for the checkbox group. Provides a hint such as specific requirements for what to choose. + error_message: An error message to be displayed when the checkbox group is an errored state. + is_required: Whether user input is required on the input before form submission. + is_invalid: Whether the checkbox group is in an invalid state. + validation_behavior: Whether to use native HTML form validation to prevent form + submission when the value is missing or invalid, + or mark the field as required or invalid via ARIA. + 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. + necessity_indicator: Whether the required state should be shown as an icon or text. + contextual_help: A ContextualHelp element to place next to the label. + show_error_icon: Whether an error icon is rendered. + on_change: Handler that is called when the selection changes. + on_focus: Handler that is called when the element receives focus. + on_blur: Handler that is called when the element loses focus. + on_focus_change: Handler that is called when the element's focus status changes. + 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. + 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 position. + top: The top position of the element. + bottom: The bottom position of the element. + left: The left position of the element. + right: The right position of the element. + start: The logical start position of the element, depending on layout direction. + end: The logical end position of the element, depending on layout direction. + z_index: The stacking order for the element + is_hidden: Hides the element. + id: The unique identifier of the element. + aria_label: Defines a string value that labels the current element. + aria_labelledby: Identifies the element (or elements) that labels the current element. + aria_describedby: Identifies the element (or elements) that describes the object. + aria_details: Identifies the element (or elements) that provide a detailed, extended description for the object. + aria_errormessage: Identifies the element that provides an error message for the object. + UNSAFE_class_name: Set the CSS className for the element. Only use as a last resort. Use style props instead. + UNSAFE_style: Set the inline style for the element. Only use as a last resort. Use style props instead. + + Returns: + The rendered checkbox group element. + + """ + return component_element( + "CheckboxGroup", + children=children, + orientation=orientation, + is_emphasized=is_emphasized, + value=value, + default_value=default_value, + is_disabled=is_disabled, + is_read_only=is_read_only, + name=name, + label=label, + description=description, + error_message=error_message, + is_required=is_required, + is_invalid=is_invalid, + validation_behavior=validation_behavior, + label_position=label_position, + label_align=label_align, + necessity_indicator=necessity_indicator, + contextual_help=contextual_help, + show_error_icon=show_error_icon, + on_change=on_change, + on_focus=on_focus, + on_blur=on_blur, + on_focus_change=on_focus_change, + 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_column=grid_column, + 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, + height=height, + min_width=min_width, + min_height=min_height, + max_width=max_width, + 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_labelledby=aria_labelledby, + aria_describedby=aria_describedby, + aria_details=aria_details, + aria_errormessage=aria_errormessage, + UNSAFE_class_name=UNSAFE_class_name, + UNSAFE_style=UNSAFE_style, + ) diff --git a/plugins/ui/src/deephaven/ui/components/combo_box.py b/plugins/ui/src/deephaven/ui/components/combo_box.py index 7fe4ef89e..80defb967 100644 --- a/plugins/ui/src/deephaven/ui/components/combo_box.py +++ b/plugins/ui/src/deephaven/ui/components/combo_box.py @@ -72,7 +72,7 @@ def combo_box( validation_state: ValidationState | None = None, label_position: LabelPosition = "top", label_align: Alignment | None = None, - necessity_indicator: NecessityIndicator | None = "icon", + necessity_indicator: NecessityIndicator | None = None, contextual_help: Element | None = None, on_open_change: Callable[[bool, MenuTriggerAction], None] | None = None, on_selection_change: Callable[[Key], None] | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/contextual_help.py b/plugins/ui/src/deephaven/ui/components/contextual_help.py index 8de37b75e..d9669d7b6 100644 --- a/plugins/ui/src/deephaven/ui/components/contextual_help.py +++ b/plugins/ui/src/deephaven/ui/components/contextual_help.py @@ -17,7 +17,10 @@ def contextual_help( - *children: Any, + heading: Any, + content: Any, + footer: Any = None, + *, variant: ContextualHelperVariant | None = "help", placement: Placement | None = "bottom start", is_open: bool | None = None, @@ -75,7 +78,9 @@ def contextual_help( """ A contextual help is a quiet action button that triggers an informational popover. Args: - *children: The children of the contextual help popover. + heading: The heading of the popover. + content: The content of the popover. + footer: The footer of the popover. variant: Indicates whether contents are informative or provides helpful guidance. placement: The placement of the popover relative to the action button. is_open: Whether the popover is open by default (controlled). @@ -132,7 +137,9 @@ def contextual_help( """ return component_element( "ContextualHelp", - *children, + heading=heading, + content=content, + footer=footer, variant=variant, placement=placement, is_open=is_open, diff --git a/plugins/ui/src/deephaven/ui/components/form.py b/plugins/ui/src/deephaven/ui/components/form.py index ad107ef5e..57c79facd 100644 --- a/plugins/ui/src/deephaven/ui/components/form.py +++ b/plugins/ui/src/deephaven/ui/components/form.py @@ -40,7 +40,7 @@ def form( auto_capitalize: AutoCapitalizeModes | None = None, label_position: LabelPosition = "top", label_align: Alignment | None = None, - necessity_indicator: NecessityIndicator = "icon", + necessity_indicator: NecessityIndicator | None = None, on_submit: Callable[[dict[str, str]], None] | None = None, on_reset: Callable[[dict[str, str]], None] | None = None, on_invalid: Callable[[dict[str, str]], None] | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/list_view.py b/plugins/ui/src/deephaven/ui/components/list_view.py index 365a40385..a7956ed00 100644 --- a/plugins/ui/src/deephaven/ui/components/list_view.py +++ b/plugins/ui/src/deephaven/ui/components/list_view.py @@ -9,7 +9,24 @@ from .._internal.utils import create_props, unpack_item_table_source from .basic import component_element from .item import Item -from ..types import ListViewDensity, Selection, SelectionMode +from ..types import ( + ListViewDensity, + ListViewOverflowMode, + Selection, + SelectionMode, + SelectionStyle, + Key, +) +from .types import ( + LoadingState, + DisabledBehavior, + LayoutFlex, + DimensionValue, + AlignSelf, + JustifySelf, + Position, + CSSProperties, +) ListViewElement = Element @@ -25,14 +42,64 @@ def list_view( *children: Item | Table | ItemTableSource, density: ListViewDensity | None = "COMPACT", - default_selected_keys: Selection | None = None, - selected_keys: Selection | None = None, - selection_mode: SelectionMode | None = "MULTIPLE", + is_quiet: bool | None = None, + loading_state: LoadingState | None = None, + overflow_mode: ListViewOverflowMode = "truncate", render_empty_state: Element | None = None, + disabled_behavior: DisabledBehavior | None = None, + disabled_keys: Selection | None = None, + selection_mode: SelectionMode | None = "MULTIPLE", + disallow_empty_selection: bool | None = None, + selected_keys: Selection | None = None, + default_selected_keys: Selection | None = None, + selection_style: SelectionStyle | None = None, + on_action: Callable[[Key, str], None] | None = None, on_selection_change: Callable[[Selection], None] | None = None, on_change: Callable[[Selection], 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_row: str | None = None, + grid_column: str | None = None, + grid_row_start: str | None = None, + grid_row_end: 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, + 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, - **props: Any, ) -> ListViewElement: """ A list view that can be used to create a list of items. Children should be one of three types: @@ -47,25 +114,65 @@ def list_view( Args: *children: The options to render within the list_view. - density: - Sets the amount of vertical padding within each cell. - default_selected_keys: - The initial selected keys in the collection (uncontrolled). - selected_keys: - The currently selected keys in the collection (controlled). - selection_mode: - By default `"MULTIPLE"`, which allows multiple selection. - May also be `"SINGLE"` to allow only single selection, or `"None"`/`None` to allow no selection. - render_empty_state: - Sets what the `list_view` should render when there is no content to display. - on_selection_change: - Handler that is called when the selection changes. - on_change: - Alias of `on_selection_change`. Handler that is called when the selection changes. - key: - A unique identifier used by React to render elements in a list. - **props: - Any other ListView prop, except items, dragAndDropHooks, and onLoadMore. + density: Sets the amount of vertical padding within each cell. + is_quiet: Whether the ListView should use the quiet style. + loading_state: The loading state of the ListView. Determines whether to show a loading spinner. + overflow_mode: The behaviour of the text when it overflows the cell. + render_empty_state: Sets what the `list_view` should render when there is no content to display. + disabled_behavior: Whether disabled_keys applies to all interactions or just selection. + disabled_keys: The keys that should be disabled. These cannot be selected, focused, or interacted with + selection_mode: By default `"MULTIPLE"`, which allows multiple selection. May also be `"SINGLE"` to allow only single selection, or `"None"`/`None` to allow no selection. + disallow_empty_selection: Whether the ListView should disallow empty selection. + selected_keys: The currently selected keys in the collection (controlled). + default_selected_keys: The initial selected keys in the collection (uncontrolled). + selection_style: How the selection should be displayed. + on_action: Handler that is called when the user performs an action on an item. The user event depends on the collection's selection_style and interaction modality. + on_change: Handler that is called when the selection changes. + on_selection_change: Deprecated. Use on_change instead.Handler that is called when the selection changes. + 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 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. + 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 top position of the element. + bottom: The bottom position of the element. + left: The left position of the element. + right: The right position of the element. + start: The logical start position of the element, depending on layout direction. + end: The logical end position of the element, depending on layout direction. + z_index: The stacking order for the element + is_hidden: Hides the element. + id: The unique identifier of the element. + aria_label: Defines a string value that labels the current element. + aria_labelledby: Identifies the element (or elements) that labels the current element. + aria_describedby: Identifies the element (or elements) that describes the object. + aria_details: Identifies the element (or elements) that provide a detailed, extended description for the object. + UNSAFE_class_name: Set the CSS className for the element. Only use as a last resort. Use style props instead. + UNSAFE_style: Set the inline style for the element. Only use as a last resort. Use style props instead. + key: A unique identifier used by React to render elements in a list. Returns: The rendered ListView. diff --git a/plugins/ui/src/deephaven/ui/components/number_field.py b/plugins/ui/src/deephaven/ui/components/number_field.py index b57b29d40..271f6e3c0 100644 --- a/plugins/ui/src/deephaven/ui/components/number_field.py +++ b/plugins/ui/src/deephaven/ui/components/number_field.py @@ -1,13 +1,10 @@ from __future__ import annotations from typing import Any, Callable from .types import ( - # Events FocusEventCallable, KeyboardEventCallable, - # Validation TextFieldValidationState, NecessityIndicator, - # Layout AlignSelf, CSSProperties, DimensionValue, @@ -15,6 +12,7 @@ LayoutFlex, Position, LabelPosition, + NumberFieldFormatOptions, Alignment, ) from .basic import component_element @@ -27,7 +25,7 @@ def number_field( decrement_aria_label: str | None = None, increment_aria_label: str | None = None, is_wheel_disabled: bool | None = None, - # format_options, # omitted because need to connect it to Deephaven formatting options as well + format_options: NumberFieldFormatOptions | None = None, is_disabled: bool | None = None, is_read_only: bool | None = None, is_required: bool | None = None, @@ -46,7 +44,7 @@ def number_field( name: str | None = None, label_position: LabelPosition = "top", label_align: Alignment | None = None, - necessity_indicator: NecessityIndicator = "icon", + necessity_indicator: NecessityIndicator | None = None, contextual_help: Any | None = None, on_focus: FocusEventCallable | None = None, on_blur: FocusEventCallable | None = None, @@ -186,6 +184,7 @@ def number_field( decrement_aria_label=decrement_aria_label, increment_aria_label=increment_aria_label, is_wheel_disabled=is_wheel_disabled, + format_options=format_options, is_disabled=is_disabled, is_read_only=is_read_only, is_required=is_required, diff --git a/plugins/ui/src/deephaven/ui/components/picker.py b/plugins/ui/src/deephaven/ui/components/picker.py index 214ef5e13..1f3fa4471 100644 --- a/plugins/ui/src/deephaven/ui/components/picker.py +++ b/plugins/ui/src/deephaven/ui/components/picker.py @@ -63,7 +63,7 @@ def picker( is_loading: bool | None = None, label_position: LabelPosition = "top", label_align: Alignment | None = None, - necessity_indicator: NecessityIndicator = "icon", + necessity_indicator: NecessityIndicator | None = None, contextual_help: Element | None = None, on_open_change: Callable[[bool], None] | None = None, on_focus: FocusEventCallable | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/radio_group.py b/plugins/ui/src/deephaven/ui/components/radio_group.py index 1ae0a1849..2567fe9ce 100644 --- a/plugins/ui/src/deephaven/ui/components/radio_group.py +++ b/plugins/ui/src/deephaven/ui/components/radio_group.py @@ -40,7 +40,7 @@ def radio_group( error_message: Any | None = None, label_position: LabelPosition = "top", label_align: Alignment | None = None, - necessity_indicator: NecessityIndicator = "icon", + necessity_indicator: NecessityIndicator | None = None, contextual_help: Any | None = None, show_error_icon: bool | None = None, on_focus: FocusEventCallable | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/table.py b/plugins/ui/src/deephaven/ui/components/table.py index 8c8857250..a30509c47 100644 --- a/plugins/ui/src/deephaven/ui/components/table.py +++ b/plugins/ui/src/deephaven/ui/components/table.py @@ -4,6 +4,7 @@ import logging from deephaven.table import Table from ..elements import Element +from .types import AlignSelf, DimensionValue, JustifySelf, LayoutFlex, Position from ..types import ( CellPressCallback, ColumnGroup, @@ -132,6 +133,43 @@ class table(Element): May also be a function that receives the column header data and returns the context menu items or None. databars: Databars are experimental and will be moved to column_formatting in the future. key: A unique identifier used by React to render elements in a list. + 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. + Returns: + The rendered Table. """ _props: dict[str, Any] @@ -170,6 +208,41 @@ def __init__( ) = None, databars: list[TableDatabar] | None = None, key: str | 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, ): props = locals() del props["self"] diff --git a/plugins/ui/src/deephaven/ui/components/text_area.py b/plugins/ui/src/deephaven/ui/components/text_area.py index 69f1b204d..67b394947 100644 --- a/plugins/ui/src/deephaven/ui/components/text_area.py +++ b/plugins/ui/src/deephaven/ui/components/text_area.py @@ -49,7 +49,7 @@ def text_area( validation_state: TextFieldValidationState | None = None, label_position: LabelPosition = "top", label_align: Alignment | None = None, - necessity_indicator: NecessityIndicator = "icon", + necessity_indicator: NecessityIndicator | None = None, contextual_help: Any | None = None, on_focus: FocusEventCallable | None = None, on_blur: FocusEventCallable | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/text_field.py b/plugins/ui/src/deephaven/ui/components/text_field.py index 83f3b1a26..52debc960 100644 --- a/plugins/ui/src/deephaven/ui/components/text_field.py +++ b/plugins/ui/src/deephaven/ui/components/text_field.py @@ -48,7 +48,7 @@ def text_field( validation_state: TextFieldValidationState | None = None, label_position: LabelPosition = "top", label_align: Alignment | None = None, - necessity_indicator: NecessityIndicator = "icon", + necessity_indicator: NecessityIndicator | None = None, contextual_help: Any | None = None, on_focus: FocusEventCallable | None = None, on_blur: FocusEventCallable | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/toggle_button.py b/plugins/ui/src/deephaven/ui/components/toggle_button.py index ebd49984c..d9f1cafc2 100644 --- a/plugins/ui/src/deephaven/ui/components/toggle_button.py +++ b/plugins/ui/src/deephaven/ui/components/toggle_button.py @@ -167,6 +167,10 @@ def toggle_button( 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 toggle button element. + """ return component_element( diff --git a/plugins/ui/src/deephaven/ui/components/types/Intl/number_field.py b/plugins/ui/src/deephaven/ui/components/types/Intl/number_field.py new file mode 100644 index 000000000..cb9849cf3 --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/types/Intl/number_field.py @@ -0,0 +1,501 @@ +from typing import Literal, Union +import sys + +if sys.version_info < (3, 11): + from typing_extensions import TypedDict, NotRequired +else: + from typing import TypedDict, NotRequired + + +NumberSystems = Literal[ + "adlm", + "ahom", + "arab", + "arabext", + "armn", + "armnlow", + "bali", + "beng", + "bhks", + "brah", + "cakm", + "cham", + "cyrl", + "deva", + "ethi", + "finance", + "fullwide", + "geor", + "gong", + "gonm", + "grek", + "greklow", + "gujr", + "guru", + "hanidays", + "hanidec", + "hans", + "hansfin", + "hant", + "hantfin", + "hebr", + "hmng", + "hmnp", + "java", + "jpan", + "jpanfin", + "jpanyear", + "kali", + "khmr", + "knda", + "lana", + "lanatham", + "laoo", + "latn", + "lepc", + "limb", + "mathbold", + "mathdbl", + "mathmono", + "mathsanb", + "mathsans", + "mlym", + "modi", + "mong", + "mroo", + "mtei", + "mymr", + "mymrshan", + "mymrtlng", + "native", + "newa", + "nkoo", + "olck", + "orya", + "osma", + "rohg", + "roman", + "romanlow", + "saur", + "shrd", + "sind", + "sinh", + "sora", + "sund", + "takr", + "talu", + "taml", + "tamldec", + "telu", + "thai", + "tirh", + "tibt", + "traditio", + "vaii", + "wara", + "wcho", +] + +CurrencyCodes = Literal[ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BYN", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CUC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "FOK", + "GBP", + "GEL", + "GGP", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "IMP", + "INR", + "IQD", + "IRR", + "ISK", + "JEP", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KID", + "KMF", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRU", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "SSP", + "STN", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TVD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VES", + "VND", + "VUV", + "WST", + "XAF", + "XCD", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW", + "ZWL", +] + +Units = Literal[ + "acre", + "bit", + "byte", + "celsius", + "centimeter", + "day", + "degree", + "fahrenheit", + "fluid-ounce", + "foot", + "gallon", + "gigabit", + "gigabyte", + "gram", + "hectare", + "hour", + "inch", + "kilobit", + "kilobyte", + "kilogram", + "kilometer", + "liter", + "megabit", + "megabyte", + "meter", + "mile", + "mile-scandinavian", + "millimeter", + "milliliter", + "millisecond", + "minute", + "month", + "ounce", + "percent", + "petabyte", + "pound", + "second", + "stone", + "terabit", + "terabyte", + "week", + "yard", + "year", +] + + +class Options(TypedDict): + """ + Options for formatting the value of a NumberField. + This also affects the characters allowed in the input. + """ + + # Locale Options + locale_matcher: NotRequired[Literal["lookup", "best fit"]] + """ + The locale matching algorithm to use. + Possible values are "lookup" to use the runtime's locale matching algorithm, or "best fit" to use the CLDR locale matching algorithm. + The default is "best fit". + """ + + numbering_matching: NotRequired[NumberSystems] + """ + The numbering system to use. + Possible values are the numbering systems specified in the Unicode CLDR, such as "arab" for Arabic-Indic digits or "latn" for Latin digits. + For more information see the supported numbering systems in the getNumberingSystems page on MDN. + + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getNumberingSystems + """ + + # Style Options + style: NotRequired[Literal["decimal", "percent", "currency", "unit"]] + """ + The formatting style to use. + Possible values are "decimal" for plain number formatting, "percent" for percent formatting, "currency" for currency formatting, or "unit" for unit formatting. + """ + + currency: NotRequired[CurrencyCodes] + """ + The currency to use in currency formatting. + Possible values are the ISO 4217 currency codes, such as "USD" for the US dollar, "EUR" for the euro, or "CNY" for the Chinese yuan. + The default is "USD". + """ + + currency_display: NotRequired[Literal["symbol", "narrowSymbol", "code", "name"]] + """ + How to display the currency in currency formatting. + Possible values are "symbol" to use a localized currency symbol such as €, "narrowSymbol" to use a narrow form of the symbol such as ƒ, "code" to use the ISO currency code, or "name" to use a localized currency name such as "dollar". + """ + + currency_sign: NotRequired[Literal["standard", "accounting"]] + """ + Determines how to display negative values in currency formatting. + In many locales, the "accounting" format wraps negative numbers in parentheses instead of using a minus sign. + Possible values are "standard" and "accounting". The default is "standard". + """ + + unit: NotRequired[Units] + """ + The unit to use in unit formatting. + Possible values are the units specified in the Unicode CLDR. Only a subset of units was selected from the full list such as "meter" for meters or "mile" for miles. + Check https://tc39.es/ecma402/#table-sanctioned-single-unit-identifiers for the full list of supported units. + """ + + unit_display: NotRequired[Literal["long", "short", "narrow"]] + """ + How to display the unit in unit formatting. + Possible values are "long" to use a full unit name such as "16 meters", "short" to use an abbreviated unit name such as "16 m", or "narrow" to use a narrow form of the unit name such as "16m". + Default is "short". + """ + + # Digit Options + minimum_integer_digits: NotRequired[int] + """ + The minimum number of integer digits to use. + Possible values are from 1 to 21. + The default is 1. + """ + + minimum_fraction_digits: NotRequired[int] + """ + The minimum number of fraction digits to use. + Possible values are from 0 to 100; the default for plain number and percent formatting is 0; the default for currency formatting is 2 if not provided in the ISO 4217 currency code list. + """ + + maximum_fraction_digits: NotRequired[int] + """ + The maximum number of fraction digits to use. + Possible values are from 0 to 100. + Plain number formatting: The default is the larger of minimum_fraction_digits and 3. + Percent formatting: The default is the larger of minimum_fraction_digits and 0. + Currency formatting: The default is the larger of minimum_fraction_digits and the number of minor unit digits in the ISO 4217 list. + """ + + minimum_significant_digits: NotRequired[int] + """ + The minimum number of significant digits to use. + Possible values are from 1 to 21. + Default is 1. + """ + + maximum_significant_digits: NotRequired[int] + """ + The maximum number of significant digits to use. + Possible values are from 1 to 21. + Default is 21. + """ + + rounding_priority: NotRequired[Literal["auto", "morePrecision", "lessPrecision"]] + """ + Specifies how rounding conflicts is resolved if both fraction_digits and significant_digits are provided. + Possible values are "auto" to use the significant digits property, "morePrecision" to prioritize rounding to a more precise number, or "lessPrecision" to prioritize rounding to a less precise number. + The default is "auto". + """ + + rounding_increment: NotRequired[ + Literal[1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 200, 2500, 5000] + ] + """ + The rounding increment to use. + Possible values are 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, or 5000. + Cannot be mixed with significant-digits rounding or any setting of rounding_priority other than auto. + """ + + rounding_mode: NotRequired[ + Literal[ + "ceil", + "floor", + "expand", + "trunc", + "halfCeil", + "halfFloor", + "halfExpand", + "halfTrunc", + "halfEven", + ] + ] + """ + The rounding mode to use. + Possible values are "ceil" to round towards positive infinity, "floor" to round towards negative infinity, "expand" to round away from zero, "trunc" to round towards zero, "halfCeil" to round towards the nearest neighbor or to positive infinity if equidistant, "halfFloor" to round towards the nearest neighbor or to negative infinity if equidistant, "halfExpand" to round towards the nearest neighbor or to infinity if equidistant, "halfTrunc" to round towards the nearest neighbor or to zero if equidistant, or "halfEven" to round towards the nearest neighbor or to the even neighbor if equidistant. + The default is "halfExpand". + """ + + trailing_zero_display: NotRequired[Literal["auto", "stripIfInteger"]] + """ + How to display trailing zeros in fraction digits. + Possible values are "auto" to display trailing zeros according to minimum_fraction_digits and minimum_significant_digits, or "stripIfInteger" to remove trailing zeros in fraction digits if they are all zero. + The default is "auto". + """ + + # Other Options + notation: NotRequired[Literal["standard", "scientific", "engineering", "compact"]] + """ + Possible values are "standard" for plain number formatting, "scientific" for order-of-magnitude for formatted number, "engineering" for the exponent of ten when divisible by three, or "compact" for string representing exponent; defaults to using the "short" form. + Default is "standard". + """ + + compact_display: NotRequired[Literal["short", "long"]] + """ + Possible values are "short" for compact notation, or "long" for full notation. + Default is "short". + """ + + use_grouping: NotRequired[Literal["always", "auto", "min2", True, False]] + """ + Whether to use grouping separators such as thousands separators. + Possible values are "always" to always use grouping separators, "auto" to use grouping separators according to the locale and the number of digits, "min2" to use grouping separators if there are more than two digits, or true and false to use grouping separators according to the locale. + Default is 'min2' if notation is 'compact', and 'auto' otherwise. + """ + + sign_display: NotRequired[ + Literal["auto", "never", "always", "exceptZero", "negative"] + ] + """ + Whether to display the sign of the number. + Possible values are "auto" to display the sign for negative numbers only, "never" to never display the sign, "always" to always display the sign, "exceptZero" to display the sign for all numbers except zero, or "negative" to display the sign for negative numbers only. + Default is "auto". + """ + + +class NumberFieldFormatOptions(TypedDict): + """ + Options for formatting the value of a NumberField. + This also affects the characters allowed in the input. + """ + + numbering_system: NotRequired[str] + + compact_display: NotRequired[Literal["short", "long"]] + + notation: NotRequired[Literal["standard", "scientific", "engineering", "compact"]] + + sign_display: NotRequired[Literal["auto", "never", "always", "exceptZero"]] + + unit: NotRequired[str] + + unit_display: NotRequired[Literal["long", "short", "narrow"]] + + currency_sign: NotRequired[Literal["standard", "accounting"]] diff --git a/plugins/ui/src/deephaven/ui/components/types/__init__.py b/plugins/ui/src/deephaven/ui/components/types/__init__.py index 4208d7bf6..12557f66e 100644 --- a/plugins/ui/src/deephaven/ui/components/types/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/types/__init__.py @@ -6,3 +6,4 @@ from .progress import * from .validate import * from .icon_types import * +from .Intl.number_field import * diff --git a/plugins/ui/src/deephaven/ui/components/types/layout.py b/plugins/ui/src/deephaven/ui/components/types/layout.py index dfccfa931..68cac2a9d 100644 --- a/plugins/ui/src/deephaven/ui/components/types/layout.py +++ b/plugins/ui/src/deephaven/ui/components/types/layout.py @@ -129,7 +129,6 @@ Overflow = Union[Literal["visible", "hidden", "clip", "scroll", "auto"], str] OverflowMode = Literal["wrap", "collapse"] - Alignment = Literal["start", "end"] ButtonGroupAlignment = Literal["start", "center", "end"] diff --git a/plugins/ui/src/deephaven/ui/components/types/validate.py b/plugins/ui/src/deephaven/ui/components/types/validate.py index 81687390d..a95ff39d9 100644 --- a/plugins/ui/src/deephaven/ui/components/types/validate.py +++ b/plugins/ui/src/deephaven/ui/components/types/validate.py @@ -22,3 +22,5 @@ AutoCompleteModes = Literal["on", "off"] AutoCapitalizeModes = Literal["off", "none", "on", "sentences", "words", "characters"] + +DisabledBehavior = Literal["selection", "all"] diff --git a/plugins/ui/src/deephaven/ui/types/types.py b/plugins/ui/src/deephaven/ui/types/types.py index 07d548ea1..7128d3b27 100644 --- a/plugins/ui/src/deephaven/ui/types/types.py +++ b/plugins/ui/src/deephaven/ui/types/types.py @@ -463,6 +463,7 @@ class SliderChange(TypedDict): TableData = Dict[ColumnName, ColumnData] SelectionArea = Literal["CELL", "ROW", "COLUMN"] SelectionMode = Literal["SINGLE", "MULTIPLE"] +SelectionStyle = Literal["checkbox", "highlight"] Sentinel = Any TransformedData = Any ActionMenuDirection = Literal["bottom", "top", "left", "right", "start", "end"] @@ -502,6 +503,7 @@ class SliderChange(TypedDict): ] Granularity = Literal["DAY", "HOUR", "MINUTE", "SECOND"] ListViewDensity = Literal["COMPACT", "NORMAL", "SPACIOUS"] +ListViewOverflowMode = Literal["truncate", "wrap"] ActionGroupDensity = Literal["compact", "regular"] TabDensity = Literal["compact", "regular"] Dependencies = Union[Tuple[Any], List[Any]] diff --git a/plugins/ui/src/js/src/elements/ContextualHelp.tsx b/plugins/ui/src/js/src/elements/ContextualHelp.tsx new file mode 100644 index 000000000..34ceb951c --- /dev/null +++ b/plugins/ui/src/js/src/elements/ContextualHelp.tsx @@ -0,0 +1,46 @@ +import { + ContextualHelp as DHCContextualHelp, + ContextualHelpProps as DHCContextualHelpProps, + Heading, + Content, + Footer, +} from '@deephaven/components'; +import { isElementOfType } from '@deephaven/react-hooks'; +import { ReactNode } from 'react'; + +export type SerializedContextualHelpProps = Omit< + DHCContextualHelpProps, + 'children' +> & { + heading: ReactNode; + content: ReactNode; + footer?: ReactNode; +}; + +export function ContextualHelp( + props: SerializedContextualHelpProps +): JSX.Element { + const { heading, content, footer, ...otherProps } = props; + + return ( + /* eslint-disable-next-line react/jsx-props-no-spreading */ + + {heading != null && + (isElementOfType(heading, Heading) ? ( + heading + ) : ( + {heading} + ))} + {content != null && + (isElementOfType(content, Content) ? ( + content + ) : ( + {content} + ))} + {footer != null && + (isElementOfType(footer, Footer) ? footer : )} + + ); +} + +export default ContextualHelp; diff --git a/plugins/ui/src/js/src/elements/UITable/UITable.tsx b/plugins/ui/src/js/src/elements/UITable/UITable.tsx index 88642810b..fbf264190 100644 --- a/plugins/ui/src/js/src/elements/UITable/UITable.tsx +++ b/plugins/ui/src/js/src/elements/UITable/UITable.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; +import classNames from 'classnames'; import { DehydratedQuickFilter, IrisGrid, @@ -11,7 +12,9 @@ import { import { colorValueStyle, resolveCssVariablesInRecord, + useStyleProps, useTheme, + viewStyleProps, } from '@deephaven/components'; import { useApi } from '@deephaven/jsapi-bootstrap'; import { TableUtils } from '@deephaven/jsapi-utils'; @@ -66,6 +69,7 @@ export function UITable({ contextMenu, contextHeaderMenu, databars: databarsProp, + ...userStyleProps }: UITableProps): JSX.Element | null { const [error, setError] = useState(null); @@ -77,6 +81,39 @@ export function UITable({ throw error; } + // Margin looks wrong with ui.table, so we want to map margin to padding instead + const { + margin, + marginTop, + marginBottom, + marginStart, + marginEnd, + marginX, + marginY, + ...restStyleProps + } = userStyleProps ?? {}; + const { styleProps } = useStyleProps( + { + padding: margin, + paddingTop: marginTop, + paddingBottom: marginBottom, + paddingStart: marginStart, + paddingEnd: marginEnd, + paddingX: marginX, + paddingY: marginY, + ...restStyleProps, + // Add min and max height if the user set height or width explicitly + // This fixes issues in flex boxes where one table is auto sized and the other explicit + // The explicit table will never reach its size because the auto sized table has width/height 100% + // We don't want to set flex-shrink because it could be the cross-axis value that is explicitly set + minHeight: restStyleProps.minHeight ?? restStyleProps.height, + maxHeight: restStyleProps.maxHeight ?? restStyleProps.height, + minWidth: restStyleProps.minWidth ?? restStyleProps.width, + maxWidth: restStyleProps.maxWidth ?? restStyleProps.width, + }, + viewStyleProps // Needed so spectrum applies styles from view instead of base which doesn't have padding + ); + const dh = useApi(); const theme = useTheme(); const [irisGrid, setIrisGrid] = useState(null); @@ -319,7 +356,11 @@ export function UITable({ useEffect(() => () => model?.close(), [model]); return model ? ( -
+
setIrisGrid(ref)} model={model} diff --git a/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx b/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx index 57a44c14f..912a49852 100644 --- a/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx +++ b/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx @@ -1,3 +1,4 @@ +import { type StyleProps } from '@react-types/shared'; import type { dh } from '@deephaven/jsapi-types'; import { ColumnName, DehydratedSort } from '@deephaven/iris-grid'; import { ELEMENT_KEY, ElementNode, isElementNode } from '../utils/ElementUtils'; @@ -47,7 +48,7 @@ export type FormattingRule = { mode?: DatabarConfig; }; -export type UITableProps = { +export type UITableProps = StyleProps & { table: dh.WidgetExportedObject; formatting?: FormattingRule[]; onCellPress?: (data: CellData) => void; @@ -72,6 +73,7 @@ export type UITableProps = { contextMenu?: ResolvableUIContextItem | ResolvableUIContextItem[]; contextHeaderMenu?: ResolvableUIContextItem | ResolvableUIContextItem[]; databars?: DatabarConfig[]; + [key: string]: unknown; // Needed because StyleProps is an interface which removes the implicit index signature of the type }; export type UITableNode = Required< diff --git a/plugins/ui/src/js/src/elements/index.ts b/plugins/ui/src/js/src/elements/index.ts index 896341621..e2a3fa596 100644 --- a/plugins/ui/src/js/src/elements/index.ts +++ b/plugins/ui/src/js/src/elements/index.ts @@ -3,6 +3,7 @@ export * from './ActionGroup'; export * from './Button'; export * from './Calendar'; export * from './ComboBox'; +export * from './ContextualHelp'; export * from './DateField'; export * from './DatePicker'; export * from './DateRangePicker'; diff --git a/plugins/ui/src/js/src/elements/model/ElementConstants.ts b/plugins/ui/src/js/src/elements/model/ElementConstants.ts index 09a417403..0b6f246f0 100644 --- a/plugins/ui/src/js/src/elements/model/ElementConstants.ts +++ b/plugins/ui/src/js/src/elements/model/ElementConstants.ts @@ -30,6 +30,7 @@ export const ELEMENT_NAME = { buttonGroup: uiComponentName('ButtonGroup'), calendar: uiComponentName('Calendar'), checkbox: uiComponentName('Checkbox'), + checkboxGroup: uiComponentName('CheckboxGroup'), comboBox: uiComponentName('ComboBox'), content: uiComponentName('Content'), contextualHelp: uiComponentName('ContextualHelp'), diff --git a/plugins/ui/src/js/src/styles.scss b/plugins/ui/src/js/src/styles.scss index 973da5aa9..4fa048b52 100644 --- a/plugins/ui/src/js/src/styles.scss +++ b/plugins/ui/src/js/src/styles.scss @@ -7,8 +7,18 @@ overflow: hidden; } -.ui-object-container { - display: contents; +.ui-table-container { + // If all browsers properly supported 'stretch' for height and width + // we could swap to that, but right now only Chrome properly implements it. + // Stretch is prefixed as -webkit-fill-available for Chrome and Webkit + // and -moz-available for Firefox. + // Firefox and Webkit only seem to apply properly stretch to width, not height. + // The benefit to swapping width/height to stretch is that a user + // specifying margin on a table would basically be treated as padding. + // This would create a better experience so tables with margin don't overflow the panel + // like they do with 100% width/height. + height: 100%; + width: 100%; position: relative; } @@ -54,7 +64,7 @@ &:has(.dh-inner-react-panel > .iris-grid:only-child), &:has( .dh-inner-react-panel - > .ui-object-container:only-child + > .ui-table-container:only-child > .iris-grid:only-child ), &:has(.dh-inner-react-panel > .chart-wrapper:only-child) { diff --git a/plugins/ui/src/js/src/widget/WidgetUtils.tsx b/plugins/ui/src/js/src/widget/WidgetUtils.tsx index edcba7560..a6c394a50 100644 --- a/plugins/ui/src/js/src/widget/WidgetUtils.tsx +++ b/plugins/ui/src/js/src/widget/WidgetUtils.tsx @@ -8,8 +8,8 @@ import { ActionMenu, ButtonGroup, SpectrumCheckbox as Checkbox, + CheckboxGroup, Content, - ContextualHelp, Heading, Item, ListActionGroup, @@ -50,6 +50,7 @@ import { Button, Calendar, ComboBox, + ContextualHelp, DateField, DatePicker, DateRangePicker, @@ -110,6 +111,7 @@ export const elementComponentMap = { [ELEMENT_NAME.buttonGroup]: ButtonGroup, [ELEMENT_NAME.calendar]: Calendar, [ELEMENT_NAME.checkbox]: Checkbox, + [ELEMENT_NAME.checkboxGroup]: CheckboxGroup, [ELEMENT_NAME.comboBox]: ComboBox, [ELEMENT_NAME.content]: Content, [ELEMENT_NAME.contextualHelp]: ContextualHelp, @@ -180,9 +182,7 @@ export function getComponentForElement(element: ElementNode): React.ReactNode { } if (props?.contextualHelp != null && isPrimitive(props.contextualHelp)) { props.contextualHelp = ( - - {props.contextualHelp} - + ); } return ; diff --git a/sphinx_ext/deephaven_autodoc.py b/sphinx_ext/deephaven_autodoc.py index 4ed206e33..5d7abc703 100644 --- a/sphinx_ext/deephaven_autodoc.py +++ b/sphinx_ext/deephaven_autodoc.py @@ -33,6 +33,7 @@ class FunctionMetadata(TypedDict): name: str +# total is False to allow for popping some keys class SignatureData(TypedDict): parameters: Params return_description: str @@ -44,6 +45,8 @@ class SignatureData(TypedDict): SignatureValue = Union[str, Params] +AUTOFUNCTION_COMMENT_PREFIX = "AutofunctionCommentPrefix:" + def extract_parameter_defaults( node: sphinx.addnodes.desc_parameterlist, @@ -103,7 +106,7 @@ def extract_list_item(node: docutils.nodes.list_item) -> ParamData: """ field = node.astext().replace("\n", " ") try: - match = re.match(r"(.+) \((.*)\) -- (.+)", field) + match = re.match(r"(.+?) \((.*?)\) -- (.+)", field, re.DOTALL) if match is None: raise ValueError( f"Could not match {field} to extract param data. " @@ -154,7 +157,7 @@ def extract_field_body( # this is still a parameter, likely the only one in its signature return [extract_list_item(child)] elif is_paragraph: - return node.astext().replace("\n", " ") + return node.astext().replace("\n", "
") elif isinstance(child, docutils.nodes.bullet_list): return extract_list_items(child) raise ValueError( @@ -225,7 +228,7 @@ def extract_content_data( if isinstance(child_node, docutils.nodes.field_list): result.update(extract_field_list(child_node)) elif isinstance(child_node, docutils.nodes.paragraph): - result["description"] = child_node.astext().replace("\n", " ") + result["description"] = child_node.astext().replace("\n", "
") return result @@ -305,11 +308,19 @@ def to_mdx(node: sphinx.addnodes.desc) -> docutils.nodes.comment: dat = json.dumps(result) - param_table = f"" + return_description = result["return_description"] + return_type = result["return_type"] + + autofunction_markdown = ( + f"{AUTOFUNCTION_COMMENT_PREFIX}" + rf"{return_description}

" + rf"**Returns:** {return_type}

" + rf"" + ) # This is a little hacky, but this way the markdown renderer will not escape the special characters # such as * and \. The comment markers will be removed by make_docs.py. - return docutils.nodes.comment("", "", docutils.nodes.raw("", param_table)) + return docutils.nodes.comment("", "", docutils.nodes.raw("", autofunction_markdown)) class DeephavenAutodoc(AutodocDirective): diff --git a/sphinx_ext/make_docs_utilities.py b/sphinx_ext/make_docs_utilities.py index ec0c4b2e0..f15c5d760 100644 --- a/sphinx_ext/make_docs_utilities.py +++ b/sphinx_ext/make_docs_utilities.py @@ -4,6 +4,7 @@ import os import contextlib from typing import IO, Generator +from deephaven_autodoc import AUTOFUNCTION_COMMENT_PREFIX BUILT_DOCS = "docs/build/markdown" @@ -111,12 +112,15 @@ def remove_paramtable_comment( Returns: str: The line with the comment markers removed """ - if line.startswith("\n"): + if line.startswith(f"\n" + ): # remove the comment markers # these are added in deephaven_autodoc.py to prevent special characters from being escaped # by the markdown renderer - line = line.replace("", "") + line = line.replace("
", "\n") return line diff --git a/tests/app.d/ui_flex.py b/tests/app.d/ui_flex.py index 0789adfe7..4c0292aff 100644 --- a/tests/app.d/ui_flex.py +++ b/tests/app.d/ui_flex.py @@ -83,3 +83,10 @@ def ui_flex_test_component(): ui.button("Test"), _t_flex, ) +flex_22 = ui.panel( + ui.flex(ui.table(_t_flex, margin="20px"), _t_flex, direction="column") +) +flex_23 = ui.panel(ui.flex(ui.table(_t_flex, margin="20px"), _t_flex, direction="row")) +flex_24 = ui.panel( + ui.flex(ui.table(_t_flex, height="200px"), _t_flex, direction="column") +) diff --git a/tests/app.d/ui_render_all.py b/tests/app.d/ui_render_all.py index f0b35f5d2..99cdba25f 100644 --- a/tests/app.d/ui_render_all.py +++ b/tests/app.d/ui_render_all.py @@ -55,7 +55,7 @@ def ui_components1(): ui.column("Column child A", "Column child B", "Column child C"), # TODO: #201 ui.combo_box("Combo Box"), ui.content("Content"), - ui.contextual_help("Contextual Help"), + ui.contextual_help("Contextual Help", "Content"), ui.date_picker(label="Date Picker", value="2021-01-01"), ui.date_range_picker( label="Date Range Picker", diff --git a/tests/ui.spec.ts b/tests/ui.spec.ts index af9461b3d..144f7bbb3 100644 --- a/tests/ui.spec.ts +++ b/tests/ui.spec.ts @@ -104,6 +104,9 @@ test.describe('UI flex components', () => { { name: 'flex_19', traces: 1 }, { name: 'flex_20', traces: 1 }, { name: 'flex_21', traces: 1 }, + { name: 'flex_22', traces: 0 }, + { name: 'flex_23', traces: 0 }, + { name: 'flex_24', traces: 0 }, ].forEach(i => { test(i.name, async ({ page }) => { await gotoPage(page, ''); diff --git a/tests/ui.spec.ts-snapshots/UI-flex-components-flex-22-1-chromium-linux.png b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-22-1-chromium-linux.png new file mode 100644 index 000000000..73ffe63c9 Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-22-1-chromium-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-flex-components-flex-22-1-firefox-linux.png b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-22-1-firefox-linux.png new file mode 100644 index 000000000..72226c1ca Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-22-1-firefox-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-flex-components-flex-22-1-webkit-linux.png b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-22-1-webkit-linux.png new file mode 100644 index 000000000..24d59bd70 Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-22-1-webkit-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-flex-components-flex-23-1-chromium-linux.png b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-23-1-chromium-linux.png new file mode 100644 index 000000000..3c7a41eac Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-23-1-chromium-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-flex-components-flex-23-1-firefox-linux.png b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-23-1-firefox-linux.png new file mode 100644 index 000000000..a18dab590 Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-23-1-firefox-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-flex-components-flex-23-1-webkit-linux.png b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-23-1-webkit-linux.png new file mode 100644 index 000000000..952d3e50e Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-23-1-webkit-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-flex-components-flex-24-1-chromium-linux.png b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-24-1-chromium-linux.png new file mode 100644 index 000000000..88a3e3aa0 Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-24-1-chromium-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-flex-components-flex-24-1-firefox-linux.png b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-24-1-firefox-linux.png new file mode 100644 index 000000000..b7e8aa542 Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-24-1-firefox-linux.png differ diff --git a/tests/ui.spec.ts-snapshots/UI-flex-components-flex-24-1-webkit-linux.png b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-24-1-webkit-linux.png new file mode 100644 index 000000000..041923f4d Binary files /dev/null and b/tests/ui.spec.ts-snapshots/UI-flex-components-flex-24-1-webkit-linux.png differ