From ab1adfb03a246aaa6998a409c6b3e2cf5569fd69 Mon Sep 17 00:00:00 2001 From: Akshat Jawne Date: Mon, 22 Jul 2024 14:50:13 -0600 Subject: [PATCH 01/10] feat: ui.text_area --- .../src/deephaven/ui/components/__init__.py | 2 + .../src/deephaven/ui/components/text_area.py | 263 ++++++++++++++++++ plugins/ui/src/js/src/elements/TextArea.tsx | 75 +++++ plugins/ui/src/js/src/elements/index.ts | 1 + .../js/src/elements/model/ElementConstants.ts | 1 + plugins/ui/src/js/src/widget/WidgetUtils.tsx | 2 + 6 files changed, 344 insertions(+) create mode 100644 plugins/ui/src/deephaven/ui/components/text_area.py create mode 100644 plugins/ui/src/js/src/elements/TextArea.tsx diff --git a/plugins/ui/src/deephaven/ui/components/__init__.py b/plugins/ui/src/deephaven/ui/components/__init__.py index 1793f1380..6d427ad8f 100644 --- a/plugins/ui/src/deephaven/ui/components/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/__init__.py @@ -43,6 +43,7 @@ from .table import table from .tabs import tabs from .text import text +from .text_area import text_area from .text_field import text_field from .toggle_button import toggle_button from .view import view @@ -95,6 +96,7 @@ "tabs", "tab", "text", + "text_area", "text_field", "toggle_button", "view", diff --git a/plugins/ui/src/deephaven/ui/components/text_area.py b/plugins/ui/src/deephaven/ui/components/text_area.py new file mode 100644 index 000000000..b073231b1 --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/text_area.py @@ -0,0 +1,263 @@ +from __future__ import annotations +from typing import Any, Callable +from .types import ( + # Accessibility + AriaHasPopup, + AriaAutoComplete, + # Events + FocusEventCallable, + KeyboardEventCallable, + # Layout + AlignSelf, + CSSProperties, + DimensionValue, + JustifySelf, + LayoutFlex, + Position, + LabelPosition, + Align, + # Validation + TextFieldType, + TextFieldInputMode, + TextFieldValidationState, + NecessityIndicator, +) +from .basic import component_element +from ..elements import Element + + +def text_area( + icon: Element | None = None, + is_quiet: bool | None = None, + is_disabled: bool | None = None, + is_read_only: bool | None = None, + is_required: bool | None = None, + description: Any | None = None, + error_message: Any | None = None, + auto_focus: bool | None = None, + value: str | None = None, + default_value: str | None = None, + label: Any | None = None, + auto_complete: str | None = None, + max_length: int | None = None, + min_length: int | None = None, + input_mode: TextFieldInputMode | None = None, + name: str | None = None, + validation_state: TextFieldValidationState | None = None, + label_position: LabelPosition = "top", + label_align: Align = "start", + necessity_indicator: NecessityIndicator = "icon", + contextual_help: Any | None = None, + on_focus: FocusEventCallable | None = None, + on_blur: FocusEventCallable | None = None, + on_focus_change: Callable[[bool], None] | None = None, + on_key_down: KeyboardEventCallable | None = None, + on_key_up: KeyboardEventCallable | None = None, + on_change: Callable[[str], 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_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, + exclude_from_tab_order: bool | None = None, + aria_active_descendant: str | None = None, + aria_auto_complete: AriaAutoComplete | None = None, + aria_haspopup: AriaHasPopup | 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, + # missing properties that are clipboard or composition events +) -> Element: + """ + TextAreas are multiline text inputs, useful for cases where users have a sizable amount of text to enter. They allow for all customizations that are available to text fields. + + Args: + icon: An icon to display at the start of the input + is_quiet: Whether the input should be displayed with a quiet style + is_disabled: Whether the input should be disabled + is_read_only: Whether the input scan be selected but not changed by the user + is_required: Whether the input is required before form submission + description: A description for the field. Provides a hint such as specific requirements for what to choose. + error_message: An error message to display when the field is invalid + auto_focus: Whether the input should be focused on page load + value: The current value of the input + default_value: The default value of the input + label: The label for the input + auto_complete: Describes the type of autocomplete functionality the input should provide + max_length: The maximum number of characters the input can accept + input_mode: Hints at the tpye of data that might be entered by the user while editing the element or its contents + name: The name of the input, used when submitting an HTML form + validation_state: Whether the input should display its "valid" or "invalid" state + label_position: The position of the label relative to the input + label_align: The alignment of the label relative to the input + necessity_indicator: Whether the required state should be shown as an icon or text + contextual_help: A ContentualHelp element to place next to the label + on_focus: Function called when the button receives focus. + on_blur: Function called when the button loses focus. + on_focus_change: Function called when the focus state changes. + on_key_down: Function called when a key is pressed. + on_key_up: Function called when a key is released. + on_change: Function called when the input value 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. + 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. + exclude_from_tab_order: Whether the element should be excluded from the tab order. + aria_active_descendant: Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application. + aria_auto_complete: Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be presented if they are made. + aria_label: The label for the element. + aria_labelled_by: The id of the element that labels the current element. + aria_described_by: The id of the element that describes the current element. + aria_details: The id of the element that provides additional information about the current element. + aria_errormessage: The id of the element that provides an error message for the current element. + UNSAFE_class_name: A CSS class to apply to the element. + UNSAFE_style: A CSS style to apply to the element. + """ + + return component_element( + "TextArea", + icon=icon, + is_quiet=is_quiet, + is_disabled=is_disabled, + is_read_only=is_read_only, + is_required=is_required, + description=description, + error_message=error_message, + auto_focus=auto_focus, + value=value, + default_value=default_value, + label=label, + auto_complete=auto_complete, + max_length=max_length, + min_length=min_length, + input_mode=input_mode, + name=name, + validation_state=validation_state, + label_position=label_position, + label_align=label_align, + necessity_indicator=necessity_indicator, + contextual_help=contextual_help, + on_focus=on_focus, + on_blur=on_blur, + on_focus_change=on_focus_change, + on_key_down=on_key_down, + on_key_up=on_key_up, + on_change=on_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_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, + exclude_from_tab_order=exclude_from_tab_order, + aria_active_descendant=aria_active_descendant, + aria_auto_complete=aria_auto_complete, + aria_haspopup=aria_haspopup, + 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/js/src/elements/TextArea.tsx b/plugins/ui/src/js/src/elements/TextArea.tsx new file mode 100644 index 000000000..905e37611 --- /dev/null +++ b/plugins/ui/src/js/src/elements/TextArea.tsx @@ -0,0 +1,75 @@ +import React, { useCallback, useState } from 'react'; +import { + TextArea as DHCTextArea, + TextAreaProps as DHCTextAreaProps, +} from '@deephaven/components'; +import Log from '@deephaven/log'; +import { useDebouncedCallback, usePrevious } from '@deephaven/react-hooks'; + +const log = Log.module('@deephaven/js-plugin-ui/TextArea'); + +const VALUE_CHANGE_DEBOUNCE = 250; + +const EMPTY_FUNCTION = () => undefined; + +interface TextFieldProps extends DHCTextAreaProps { + onChange?: (value: string) => Promise; +} + +export function TextArea(props: TextFieldProps): JSX.Element { + const { + defaultValue = '', + value: propValue, + onChange: propOnChange = EMPTY_FUNCTION, + ...otherProps + } = props; + + const [value, setValue] = useState(propValue ?? defaultValue); + const [pending, setPending] = useState(false); + const prevPropValue = usePrevious(propValue); + + // Update local value to new propValue if the server sent a new propValue and no user changes have been queued + if ( + propValue !== prevPropValue && + propValue !== value && + propValue !== undefined && + !pending + ) { + setValue(propValue); + } + + const propDebouncedOnChange = useCallback( + async (newValue: string) => { + try { + await propOnChange(newValue); + } catch (e) { + log.warn('Error returned from onChange', e); + } + setPending(false); + }, + [propOnChange] + ); + + const debouncedOnChange = useDebouncedCallback( + propDebouncedOnChange, + VALUE_CHANGE_DEBOUNCE + ); + + const onChange = useCallback( + (newValue: string) => { + setPending(true); + debouncedOnChange(newValue); + setValue(newValue); + }, + [debouncedOnChange] + ); + + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ); +} + +TextArea.displayName = 'TextArea'; + +export default TextArea; diff --git a/plugins/ui/src/js/src/elements/index.ts b/plugins/ui/src/js/src/elements/index.ts index 804669056..5df250e9b 100644 --- a/plugins/ui/src/js/src/elements/index.ts +++ b/plugins/ui/src/js/src/elements/index.ts @@ -18,5 +18,6 @@ export * from './Slider'; export * from './Tabs'; export * from './TabPanels'; export * from './TextField'; +export * from './TextArea'; export * from './UITable/UITable'; export * from './utils'; diff --git a/plugins/ui/src/js/src/elements/model/ElementConstants.ts b/plugins/ui/src/js/src/elements/model/ElementConstants.ts index 60043e49a..1a5d0e7a1 100644 --- a/plugins/ui/src/js/src/elements/model/ElementConstants.ts +++ b/plugins/ui/src/js/src/elements/model/ElementConstants.ts @@ -55,6 +55,7 @@ export const ELEMENT_NAME = { tabs: uiComponentName('Tabs'), tab: uiComponentName('Tab'), text: uiComponentName('Text'), + textArea: uiComponentName('TextArea'), textField: uiComponentName('TextField'), toggleButton: uiComponentName('ToggleButton'), view: uiComponentName('View'), diff --git a/plugins/ui/src/js/src/widget/WidgetUtils.tsx b/plugins/ui/src/js/src/widget/WidgetUtils.tsx index 97cf89a52..b52416b43 100644 --- a/plugins/ui/src/js/src/widget/WidgetUtils.tsx +++ b/plugins/ui/src/js/src/widget/WidgetUtils.tsx @@ -61,6 +61,7 @@ import { Slider, TabPanels, TextField, + TextArea, UITable, Tabs, } from '../elements'; @@ -124,6 +125,7 @@ export const elementComponentMap = { [ELEMENT_NAME.tab]: Item, [ELEMENT_NAME.tabs]: Tabs, [ELEMENT_NAME.text]: Text, + [ELEMENT_NAME.textArea]: TextArea, [ELEMENT_NAME.textField]: TextField, [ELEMENT_NAME.toggleButton]: ToggleButton, [ELEMENT_NAME.view]: View, From 620bf507b9760af04f650d6f659635e2ea590964 Mon Sep 17 00:00:00 2001 From: Akshat Jawne Date: Mon, 22 Jul 2024 14:53:41 -0600 Subject: [PATCH 02/10] small cleanup --- plugins/ui/src/deephaven/ui/components/text_area.py | 5 ++--- plugins/ui/src/js/src/elements/TextArea.tsx | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/ui/src/deephaven/ui/components/text_area.py b/plugins/ui/src/deephaven/ui/components/text_area.py index b073231b1..127238304 100644 --- a/plugins/ui/src/deephaven/ui/components/text_area.py +++ b/plugins/ui/src/deephaven/ui/components/text_area.py @@ -17,7 +17,6 @@ LabelPosition, Align, # Validation - TextFieldType, TextFieldInputMode, TextFieldValidationState, NecessityIndicator, @@ -113,8 +112,8 @@ def text_area( is_disabled: Whether the input should be disabled is_read_only: Whether the input scan be selected but not changed by the user is_required: Whether the input is required before form submission - description: A description for the field. Provides a hint such as specific requirements for what to choose. - error_message: An error message to display when the field is invalid + description: A description for the area. Provides a hint such as specific requirements for what to choose. + error_message: An error message to display when the area is invalid auto_focus: Whether the input should be focused on page load value: The current value of the input default_value: The default value of the input diff --git a/plugins/ui/src/js/src/elements/TextArea.tsx b/plugins/ui/src/js/src/elements/TextArea.tsx index 905e37611..02ec858f5 100644 --- a/plugins/ui/src/js/src/elements/TextArea.tsx +++ b/plugins/ui/src/js/src/elements/TextArea.tsx @@ -12,11 +12,11 @@ const VALUE_CHANGE_DEBOUNCE = 250; const EMPTY_FUNCTION = () => undefined; -interface TextFieldProps extends DHCTextAreaProps { +interface TextAreaProps extends DHCTextAreaProps { onChange?: (value: string) => Promise; } -export function TextArea(props: TextFieldProps): JSX.Element { +export function TextArea(props: TextAreaProps): JSX.Element { const { defaultValue = '', value: propValue, From c6563a7c3e48fa50dd3b1ab090cf0b3384e7eb12 Mon Sep 17 00:00:00 2001 From: Akshat Jawne Date: Wed, 24 Jul 2024 23:52:57 -0600 Subject: [PATCH 03/10] address changes --- plugins/ui/src/js/src/elements/TextArea.tsx | 59 ++++--------------- plugins/ui/src/js/src/elements/TextField.tsx | 59 ++++--------------- plugins/ui/src/js/src/elements/hooks/index.ts | 1 + .../elements/hooks/useDebouncedOnChange.ts | 56 ++++++++++++++++++ 4 files changed, 81 insertions(+), 94 deletions(-) create mode 100644 plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts diff --git a/plugins/ui/src/js/src/elements/TextArea.tsx b/plugins/ui/src/js/src/elements/TextArea.tsx index 02ec858f5..3d2b611e1 100644 --- a/plugins/ui/src/js/src/elements/TextArea.tsx +++ b/plugins/ui/src/js/src/elements/TextArea.tsx @@ -1,14 +1,9 @@ -import React, { useCallback, useState } from 'react'; +import React from 'react'; import { TextArea as DHCTextArea, TextAreaProps as DHCTextAreaProps, } from '@deephaven/components'; -import Log from '@deephaven/log'; -import { useDebouncedCallback, usePrevious } from '@deephaven/react-hooks'; - -const log = Log.module('@deephaven/js-plugin-ui/TextArea'); - -const VALUE_CHANGE_DEBOUNCE = 250; +import useDebouncedOnChange from './hooks/useDebouncedOnChange'; const EMPTY_FUNCTION = () => undefined; @@ -24,49 +19,19 @@ export function TextArea(props: TextAreaProps): JSX.Element { ...otherProps } = props; - const [value, setValue] = useState(propValue ?? defaultValue); - const [pending, setPending] = useState(false); - const prevPropValue = usePrevious(propValue); - - // Update local value to new propValue if the server sent a new propValue and no user changes have been queued - if ( - propValue !== prevPropValue && - propValue !== value && - propValue !== undefined && - !pending - ) { - setValue(propValue); - } - - const propDebouncedOnChange = useCallback( - async (newValue: string) => { - try { - await propOnChange(newValue); - } catch (e) { - log.warn('Error returned from onChange', e); - } - setPending(false); - }, - [propOnChange] - ); - - const debouncedOnChange = useDebouncedCallback( - propDebouncedOnChange, - VALUE_CHANGE_DEBOUNCE - ); - - const onChange = useCallback( - (newValue: string) => { - setPending(true); - debouncedOnChange(newValue); - setValue(newValue); - }, - [debouncedOnChange] + const [value, onChange] = useDebouncedOnChange( + propValue, + defaultValue, + propOnChange ); return ( - // eslint-disable-next-line react/jsx-props-no-spreading - + ); } diff --git a/plugins/ui/src/js/src/elements/TextField.tsx b/plugins/ui/src/js/src/elements/TextField.tsx index ae8800d01..f8f1c2b13 100644 --- a/plugins/ui/src/js/src/elements/TextField.tsx +++ b/plugins/ui/src/js/src/elements/TextField.tsx @@ -1,14 +1,9 @@ -import React, { useCallback, useState } from 'react'; +import React from 'react'; import { TextField as DHCTextField, TextFieldProps as DHCTextFieldProps, } from '@deephaven/components'; -import Log from '@deephaven/log'; -import { useDebouncedCallback, usePrevious } from '@deephaven/react-hooks'; - -const log = Log.module('@deephaven/js-plugin-ui/TextField'); - -const VALUE_CHANGE_DEBOUNCE = 250; +import useDebouncedOnChange from './hooks/useDebouncedOnChange'; const EMPTY_FUNCTION = () => undefined; @@ -24,49 +19,19 @@ export function TextField(props: TextFieldProps): JSX.Element { ...otherProps } = props; - const [value, setValue] = useState(propValue ?? defaultValue); - const [pending, setPending] = useState(false); - const prevPropValue = usePrevious(propValue); - - // Update local value to new propValue if the server sent a new propValue and no user changes have been queued - if ( - propValue !== prevPropValue && - propValue !== value && - propValue !== undefined && - !pending - ) { - setValue(propValue); - } - - const propDebouncedOnChange = useCallback( - async (newValue: string) => { - try { - await propOnChange(newValue); - } catch (e) { - log.warn('Error returned from onChange', e); - } - setPending(false); - }, - [propOnChange] - ); - - const debouncedOnChange = useDebouncedCallback( - propDebouncedOnChange, - VALUE_CHANGE_DEBOUNCE - ); - - const onChange = useCallback( - (newValue: string) => { - setPending(true); - debouncedOnChange(newValue); - setValue(newValue); - }, - [debouncedOnChange] + const [value, onChange] = useDebouncedOnChange( + propValue, + defaultValue, + propOnChange ); return ( - // eslint-disable-next-line react/jsx-props-no-spreading - + ); } diff --git a/plugins/ui/src/js/src/elements/hooks/index.ts b/plugins/ui/src/js/src/elements/hooks/index.ts index b678c77a3..6ff897593 100644 --- a/plugins/ui/src/js/src/elements/hooks/index.ts +++ b/plugins/ui/src/js/src/elements/hooks/index.ts @@ -6,3 +6,4 @@ export * from './usePickerProps'; export * from './usePressEventCallback'; export * from './useReExportedTable'; export * from './useSelectionProps'; +export * from './useDebouncedOnChange'; diff --git a/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts b/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts new file mode 100644 index 000000000..13d363f53 --- /dev/null +++ b/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts @@ -0,0 +1,56 @@ +import { useState, useCallback } from 'react'; +import Log from '@deephaven/log'; +import { useDebouncedCallback, usePrevious } from '@deephaven/react-hooks'; + +const VALUE_CHANGE_DEBOUNCE = 250; + +function useDebouncedOnChange( + propValue: T | undefined, + defaultValue: T, + propOnChange: (() => undefined) | ((newValue: T) => Promise) +): [T, (newValue: T) => void] { + const [value, setValue] = useState(propValue ?? defaultValue); + const [pending, setPending] = useState(false); + const prevPropValue = usePrevious(propValue); + const log = Log.module('@deephaven/js-plugin-ui/useDebouncedValue'); + + // Update local value to new propValue if the server sent a new propValue and no user changes have been queued + if ( + propValue !== prevPropValue && + propValue !== value && + propValue !== undefined && + !pending + ) { + setValue(propValue); + } + + const propDebouncedOnChange = useCallback( + async (newValue: T) => { + try { + await propOnChange(newValue); + } catch (e) { + log.warn('Error returned from onChange', e); + } + setPending(false); + }, + [propOnChange] + ); + + const debouncedOnChange = useDebouncedCallback( + propDebouncedOnChange, + VALUE_CHANGE_DEBOUNCE + ); + + const onChange = useCallback( + (newValue: T) => { + setPending(true); + debouncedOnChange(newValue); + setValue(newValue); + }, + [debouncedOnChange] + ); + + return [value, onChange] as const; +} + +export default useDebouncedOnChange; From 0f0f3ca7d8e3016182f89127ceca677d09fcb27e Mon Sep 17 00:00:00 2001 From: Akshat Jawne Date: Thu, 25 Jul 2024 07:18:32 -0600 Subject: [PATCH 04/10] fix error --- plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts b/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts index 13d363f53..bfc85dad4 100644 --- a/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts +++ b/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts @@ -33,6 +33,7 @@ function useDebouncedOnChange( } setPending(false); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [propOnChange] ); From c1a9debccd000cd8cc5e2468d3e8b4a422d47ed5 Mon Sep 17 00:00:00 2001 From: Akshat Jawne Date: Fri, 26 Jul 2024 10:07:08 -0600 Subject: [PATCH 05/10] remove defaultVal prop --- plugins/ui/src/js/src/elements/TextArea.tsx | 3 +-- plugins/ui/src/js/src/elements/TextField.tsx | 3 +-- plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts | 5 ++--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/plugins/ui/src/js/src/elements/TextArea.tsx b/plugins/ui/src/js/src/elements/TextArea.tsx index 3d2b611e1..b379e7a19 100644 --- a/plugins/ui/src/js/src/elements/TextArea.tsx +++ b/plugins/ui/src/js/src/elements/TextArea.tsx @@ -20,8 +20,7 @@ export function TextArea(props: TextAreaProps): JSX.Element { } = props; const [value, onChange] = useDebouncedOnChange( - propValue, - defaultValue, + propValue ?? defaultValue, propOnChange ); diff --git a/plugins/ui/src/js/src/elements/TextField.tsx b/plugins/ui/src/js/src/elements/TextField.tsx index f8f1c2b13..d8508d074 100644 --- a/plugins/ui/src/js/src/elements/TextField.tsx +++ b/plugins/ui/src/js/src/elements/TextField.tsx @@ -20,8 +20,7 @@ export function TextField(props: TextFieldProps): JSX.Element { } = props; const [value, onChange] = useDebouncedOnChange( - propValue, - defaultValue, + propValue ?? defaultValue, propOnChange ); diff --git a/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts b/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts index bfc85dad4..ada014ae6 100644 --- a/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts +++ b/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts @@ -5,11 +5,10 @@ import { useDebouncedCallback, usePrevious } from '@deephaven/react-hooks'; const VALUE_CHANGE_DEBOUNCE = 250; function useDebouncedOnChange( - propValue: T | undefined, - defaultValue: T, + propValue: T, propOnChange: (() => undefined) | ((newValue: T) => Promise) ): [T, (newValue: T) => void] { - const [value, setValue] = useState(propValue ?? defaultValue); + const [value, setValue] = useState(propValue); const [pending, setPending] = useState(false); const prevPropValue = usePrevious(propValue); const log = Log.module('@deephaven/js-plugin-ui/useDebouncedValue'); From ed6a525bc703ca73eb98c49e266598aecf406441 Mon Sep 17 00:00:00 2001 From: Akshat Jawne Date: Fri, 26 Jul 2024 14:16:45 -0600 Subject: [PATCH 06/10] adjust icon prop to also be able to take str --- .../ui/src/deephaven/ui/components/text_area.py | 2 +- plugins/ui/src/js/src/elements/TextArea.tsx | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/ui/src/deephaven/ui/components/text_area.py b/plugins/ui/src/deephaven/ui/components/text_area.py index 127238304..286276dc7 100644 --- a/plugins/ui/src/deephaven/ui/components/text_area.py +++ b/plugins/ui/src/deephaven/ui/components/text_area.py @@ -26,7 +26,7 @@ def text_area( - icon: Element | None = None, + icon: Element | str | None = None, is_quiet: bool | None = None, is_disabled: bool | None = None, is_read_only: bool | None = None, diff --git a/plugins/ui/src/js/src/elements/TextArea.tsx b/plugins/ui/src/js/src/elements/TextArea.tsx index b379e7a19..a0c9684f3 100644 --- a/plugins/ui/src/js/src/elements/TextArea.tsx +++ b/plugins/ui/src/js/src/elements/TextArea.tsx @@ -2,8 +2,11 @@ import React from 'react'; import { TextArea as DHCTextArea, TextAreaProps as DHCTextAreaProps, + Icon, } from '@deephaven/components'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import useDebouncedOnChange from './hooks/useDebouncedOnChange'; +import { getIcon } from './utils/IconElementUtils'; const EMPTY_FUNCTION = () => undefined; @@ -16,6 +19,7 @@ export function TextArea(props: TextAreaProps): JSX.Element { defaultValue = '', value: propValue, onChange: propOnChange = EMPTY_FUNCTION, + icon: propIcon, ...otherProps } = props; @@ -24,10 +28,20 @@ export function TextArea(props: TextAreaProps): JSX.Element { propOnChange ); + const icon = + typeof propIcon === 'string' ? ( + + + + ) : ( + propIcon + ); + return ( From 49c8d809b249a0e20165310bfd9804e6a994195e Mon Sep 17 00:00:00 2001 From: Akshat Jawne Date: Fri, 26 Jul 2024 14:23:45 -0600 Subject: [PATCH 07/10] add icon type alias --- plugins/ui/src/deephaven/ui/components/text_area.py | 4 +++- plugins/ui/src/deephaven/ui/types/types.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/ui/src/deephaven/ui/components/text_area.py b/plugins/ui/src/deephaven/ui/components/text_area.py index 286276dc7..b3f5d5456 100644 --- a/plugins/ui/src/deephaven/ui/components/text_area.py +++ b/plugins/ui/src/deephaven/ui/components/text_area.py @@ -21,12 +21,14 @@ TextFieldValidationState, NecessityIndicator, ) + +from ..types import Icon from .basic import component_element from ..elements import Element def text_area( - icon: Element | str | None = None, + icon: Element | Icon | None = None, is_quiet: bool | None = None, is_disabled: bool | None = None, is_read_only: bool | None = None, diff --git a/plugins/ui/src/deephaven/ui/types/types.py b/plugins/ui/src/deephaven/ui/types/types.py index dd770a023..52a483f15 100644 --- a/plugins/ui/src/deephaven/ui/types/types.py +++ b/plugins/ui/src/deephaven/ui/types/types.py @@ -27,6 +27,8 @@ HexColor = str Color = Union[DeephavenColor, HexColor] +Icon = str + class CellData(TypedDict): """ From b3461d27a7d0afd77df09e62468053d32500d794 Mon Sep 17 00:00:00 2001 From: Akshat Jawne Date: Mon, 29 Jul 2024 09:14:47 -0600 Subject: [PATCH 08/10] small cleanup --- plugins/ui/src/deephaven/ui/types/types.py | 1 + plugins/ui/src/js/src/elements/TextArea.tsx | 3 ++- plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/ui/src/deephaven/ui/types/types.py b/plugins/ui/src/deephaven/ui/types/types.py index 52a483f15..20d57ecb3 100644 --- a/plugins/ui/src/deephaven/ui/types/types.py +++ b/plugins/ui/src/deephaven/ui/types/types.py @@ -27,6 +27,7 @@ HexColor = str Color = Union[DeephavenColor, HexColor] +# TODO: Use list of available icons once created Icon = str diff --git a/plugins/ui/src/js/src/elements/TextArea.tsx b/plugins/ui/src/js/src/elements/TextArea.tsx index a0c9684f3..67b972884 100644 --- a/plugins/ui/src/js/src/elements/TextArea.tsx +++ b/plugins/ui/src/js/src/elements/TextArea.tsx @@ -4,11 +4,12 @@ import { TextAreaProps as DHCTextAreaProps, Icon, } from '@deephaven/components'; +import { EMPTY_FUNCTION } from '@deephaven/utils'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import useDebouncedOnChange from './hooks/useDebouncedOnChange'; import { getIcon } from './utils/IconElementUtils'; -const EMPTY_FUNCTION = () => undefined; +// const EMPTY_FUNCTION = () => undefined; interface TextAreaProps extends DHCTextAreaProps { onChange?: (value: string) => Promise; diff --git a/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts b/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts index ada014ae6..b81f5f150 100644 --- a/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts +++ b/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts @@ -6,7 +6,7 @@ const VALUE_CHANGE_DEBOUNCE = 250; function useDebouncedOnChange( propValue: T, - propOnChange: (() => undefined) | ((newValue: T) => Promise) + propOnChange: (() => void) | ((newValue: T) => Promise) ): [T, (newValue: T) => void] { const [value, setValue] = useState(propValue); const [pending, setPending] = useState(false); From d76de230a6b801c4602e487342718fb83ca22a8f Mon Sep 17 00:00:00 2001 From: Akshat Jawne Date: Mon, 29 Jul 2024 09:19:18 -0600 Subject: [PATCH 09/10] remove comment --- plugins/ui/src/js/src/elements/TextArea.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/ui/src/js/src/elements/TextArea.tsx b/plugins/ui/src/js/src/elements/TextArea.tsx index 67b972884..214616cc2 100644 --- a/plugins/ui/src/js/src/elements/TextArea.tsx +++ b/plugins/ui/src/js/src/elements/TextArea.tsx @@ -9,8 +9,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import useDebouncedOnChange from './hooks/useDebouncedOnChange'; import { getIcon } from './utils/IconElementUtils'; -// const EMPTY_FUNCTION = () => undefined; - interface TextAreaProps extends DHCTextAreaProps { onChange?: (value: string) => Promise; } From fffacab6b6d6da692d4d003f3350371b3f8f823c Mon Sep 17 00:00:00 2001 From: Akshat Jawne Date: Mon, 29 Jul 2024 18:51:08 -0600 Subject: [PATCH 10/10] make changes based on review --- .../ui/src/deephaven/ui/components/text_area.py | 4 +++- plugins/ui/src/deephaven/ui/types/types.py | 2 +- plugins/ui/src/js/src/elements/TextArea.tsx | 16 +--------------- .../src/elements/hooks/useDebouncedOnChange.ts | 2 +- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/plugins/ui/src/deephaven/ui/components/text_area.py b/plugins/ui/src/deephaven/ui/components/text_area.py index b3f5d5456..19b5e490e 100644 --- a/plugins/ui/src/deephaven/ui/components/text_area.py +++ b/plugins/ui/src/deephaven/ui/components/text_area.py @@ -26,6 +26,8 @@ from .basic import component_element from ..elements import Element +from .icon import icon as icon_component + def text_area( icon: Element | Icon | None = None, @@ -186,7 +188,7 @@ def text_area( return component_element( "TextArea", - icon=icon, + icon=icon_component(icon) if type(icon) == str else icon, is_quiet=is_quiet, is_disabled=is_disabled, is_read_only=is_read_only, diff --git a/plugins/ui/src/deephaven/ui/types/types.py b/plugins/ui/src/deephaven/ui/types/types.py index 20d57ecb3..45ff11300 100644 --- a/plugins/ui/src/deephaven/ui/types/types.py +++ b/plugins/ui/src/deephaven/ui/types/types.py @@ -27,7 +27,7 @@ HexColor = str Color = Union[DeephavenColor, HexColor] -# TODO: Use list of available icons once created +# TODO #601: Use list of available icons once created Icon = str diff --git a/plugins/ui/src/js/src/elements/TextArea.tsx b/plugins/ui/src/js/src/elements/TextArea.tsx index 214616cc2..24cbec26e 100644 --- a/plugins/ui/src/js/src/elements/TextArea.tsx +++ b/plugins/ui/src/js/src/elements/TextArea.tsx @@ -2,12 +2,9 @@ import React from 'react'; import { TextArea as DHCTextArea, TextAreaProps as DHCTextAreaProps, - Icon, } from '@deephaven/components'; import { EMPTY_FUNCTION } from '@deephaven/utils'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import useDebouncedOnChange from './hooks/useDebouncedOnChange'; -import { getIcon } from './utils/IconElementUtils'; interface TextAreaProps extends DHCTextAreaProps { onChange?: (value: string) => Promise; @@ -18,29 +15,18 @@ export function TextArea(props: TextAreaProps): JSX.Element { defaultValue = '', value: propValue, onChange: propOnChange = EMPTY_FUNCTION, - icon: propIcon, ...otherProps } = props; - const [value, onChange] = useDebouncedOnChange( + const [value, onChange] = useDebouncedOnChange( propValue ?? defaultValue, propOnChange ); - const icon = - typeof propIcon === 'string' ? ( - - - - ) : ( - propIcon - ); - return ( diff --git a/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts b/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts index b81f5f150..302282010 100644 --- a/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts +++ b/plugins/ui/src/js/src/elements/hooks/useDebouncedOnChange.ts @@ -4,7 +4,7 @@ import { useDebouncedCallback, usePrevious } from '@deephaven/react-hooks'; const VALUE_CHANGE_DEBOUNCE = 250; -function useDebouncedOnChange( +function useDebouncedOnChange( propValue: T, propOnChange: (() => void) | ((newValue: T) => Promise) ): [T, (newValue: T) => void] {