diff --git a/.changeset/two-pigs-flow.md b/.changeset/two-pigs-flow.md new file mode 100644 index 0000000000..5899ad4d6c --- /dev/null +++ b/.changeset/two-pigs-flow.md @@ -0,0 +1,6 @@ +--- +'@commercetools-uikit/multiline-text-input': minor +'@commercetools-uikit/input-utils': minor +--- + +Multiple adjustments introduced to the multiline text input which now allows users to be able to add an action icon button of choice to the top right position of the text area, add the condensed feature to accomodate more text and finally, limit text rows as a way of controlling text area height. diff --git a/packages/components/inputs/input-utils/src/multiline-input/multiline-input.styles.ts b/packages/components/inputs/input-utils/src/multiline-input/multiline-input.styles.ts index 63bcc2ce54..d00a42db91 100644 --- a/packages/components/inputs/input-utils/src/multiline-input/multiline-input.styles.ts +++ b/packages/components/inputs/input-utils/src/multiline-input/multiline-input.styles.ts @@ -23,6 +23,7 @@ const getTextareaStyles = (props: TMultiLineInputProps) => { word-break: break-word; white-space: pre-wrap; resize: none; + overflow: auto; `, ]; return baseStyles; diff --git a/packages/components/inputs/input-utils/src/multiline-input/multiline-input.tsx b/packages/components/inputs/input-utils/src/multiline-input/multiline-input.tsx index 97af7f537e..9bcc8b8c71 100644 --- a/packages/components/inputs/input-utils/src/multiline-input/multiline-input.tsx +++ b/packages/components/inputs/input-utils/src/multiline-input/multiline-input.tsx @@ -26,6 +26,7 @@ export type TMultiLineInputProps = { isOpen: boolean; cacheMeasurements?: boolean; onHeightChange?: (height: number, rowCount: number) => void; + maxRows?: number; /** * Indicate if the value entered in the input is invalid. */ @@ -104,7 +105,7 @@ const MultilineInput = (props: TMultiLineInputProps) => { aria-errormessage={props['aria-errormessage']} role="textbox" minRows={MIN_ROW_COUNT} - maxRows={props.isOpen ? undefined : MIN_ROW_COUNT} + maxRows={props.isOpen ? props.maxRows : MIN_ROW_COUNT} cacheMeasurements={props.cacheMeasurements} {...filterDataAttributes(props)} /> diff --git a/packages/components/inputs/multiline-text-input/README.md b/packages/components/inputs/multiline-text-input/README.md index e7660276be..72b9e99d71 100644 --- a/packages/components/inputs/multiline-text-input/README.md +++ b/packages/components/inputs/multiline-text-input/README.md @@ -48,25 +48,29 @@ export default Example; ## Properties -| Props | Type | Required | Default | Description | -| ---------------------------- | -------------------------------------------------------------------------------------------- | :------: | ------- | ------------------------------------------------------------------------------------------------------------------------- | -| `name` | `string` | | | Used as HTML name of the input component. property | -| `aria-invalid` | `boolean` | | | Indicate if the value entered in the input is invalid. | -| `aria-errormessage` | `string` | | | HTML ID of an element containing an error message related to the input. | -| `autoComplete` | `string` | | | Used as HTML `autocomplete` property | -| `id` | `string` | | | Used as HTML id property. An id is auto-generated when it is not specified. | -| `value` | `string` | ✅ | | Value of the input component. | -| `onChange` | `ChangeEventHandler` | | | Called with an event containing the new value. Required when input is not read only. Parent should pass it back as value. | -| `onBlur` | `FocusEventHandler` | | | Called when input is blurred | -| `onFocus` | `FocusEventHandler` | | | Called when input is focused | -| `isAutofocussed` | `boolean` | | | Focus the input on initial render | -| `defaultExpandMultilineText` | `boolean` | | `false` | Expands multiline text input initially | -| `isDisabled` | `boolean` | | | Indicates that the input cannot be modified (e.g not authorized, or changes currently saving). | -| `isReadOnly` | `boolean` | | | Indicates that the field is displaying read-only content | -| `placeholder` | `string` | | | Placeholder text for the input | -| `hasError` | `boolean` | | | Indicates that input has errors | -| `hasWarning` | `boolean` | | | Control to indicate on the input if there are selected values that are potentially invalid | -| `horizontalConstraint` | `union`
Possible values:
`, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 'scale', 'auto'` | | | Horizontal size limit of the input fields. | +| Props | Type | Required | Default | Description | +| ---------------------------- | -------------------------------------------------------------------------------------------- | :------: | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | `string` | | | Used as HTML name of the input component. property | +| `aria-invalid` | `boolean` | | | Indicate if the value entered in the input is invalid. | +| `aria-errormessage` | `string` | | | HTML ID of an element containing an error message related to the input. | +| `autoComplete` | `string` | | | Used as HTML `autocomplete` property | +| `id` | `string` | | | Used as HTML id property. An id is auto-generated when it is not specified. | +| `value` | `string` | ✅ | | Value of the input component. | +| `onChange` | `ChangeEventHandler` | | | Called with an event containing the new value. Required when input is not read only. Parent should pass it back as value. | +| `onBlur` | `FocusEventHandler` | | | Called when input is blurred | +| `onFocus` | `FocusEventHandler` | | | Called when input is focused | +| `isAutofocussed` | `boolean` | | | Focus the input on initial render | +| `defaultExpandMultilineText` | `boolean` | | `false` | Expands multiline text input initially | +| `isDisabled` | `boolean` | | | Indicates that the input cannot be modified (e.g not authorized, or changes currently saving). | +| `isReadOnly` | `boolean` | | | Indicates that the field is displaying read-only content | +| `placeholder` | `string` | | | Placeholder text for the input | +| `hasError` | `boolean` | | | Indicates that input has errors | +| `hasWarning` | `boolean` | | | Control to indicate on the input if there are selected values that are potentially invalid | +| `horizontalConstraint` | `union`
Possible values:
`, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 'scale', 'auto'` | | | Horizontal size limit of the input fields. | +| `rightActionIcon` | `ReactElement` | | | Custom action icon to be displayed on the right side of the input. | +| `rightActionProps` | `TSecondaryButtonIconProps` | | | Props for the right-action icon-button. Required when rightActionIcon is provided. At least a `label` and an `onClick` prop/function need to be provided. | +| `isCondensed` | `boolean` | | | Set this to `true` to reduce the paddings of the input allowing the input to display more data in less space. | +| `maxRows` | `number` | | | Set this to value to determine the maximum text rows of the text area. Any text overflow past this row number would implement a scroll | ## Static methods diff --git a/packages/components/inputs/multiline-text-input/package.json b/packages/components/inputs/multiline-text-input/package.json index 3e17fa2f2f..7c0c2be2a8 100644 --- a/packages/components/inputs/multiline-text-input/package.json +++ b/packages/components/inputs/multiline-text-input/package.json @@ -27,6 +27,7 @@ "@commercetools-uikit/hooks": "19.5.0", "@commercetools-uikit/icons": "19.5.0", "@commercetools-uikit/input-utils": "19.5.0", + "@commercetools-uikit/secondary-icon-button": "19.5.0", "@commercetools-uikit/spacings-inline": "19.5.0", "@commercetools-uikit/spacings-stack": "19.5.0", "@commercetools-uikit/tooltip": "19.5.0", diff --git a/packages/components/inputs/multiline-text-input/src/multiline-text-input.story.js b/packages/components/inputs/multiline-text-input/src/multiline-text-input.story.js index df3dc2829c..c589acd472 100644 --- a/packages/components/inputs/multiline-text-input/src/multiline-text-input.story.js +++ b/packages/components/inputs/multiline-text-input/src/multiline-text-input.story.js @@ -1,3 +1,4 @@ +import { createElement } from 'react'; import { Value } from 'react-value'; import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; @@ -6,6 +7,12 @@ import Constraints from '@commercetools-uikit/constraints'; import Section from '../../../../../docs/.storybook/decorators/section'; import Readme from '../README.md'; import MultilineTextInput from './multiline-text-input'; +import * as icons from '@commercetools-uikit/icons'; + +const iconNames = Object.keys(icons); +const rows = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, +]; storiesOf('Components|Inputs', module) .addDecorator(withKnobs) @@ -43,6 +50,8 @@ storiesOf('Components|Inputs', module) )} isDisabled={boolean('isDisabled', false)} isReadOnly={boolean('isReadOnly', false)} + isCondensed={boolean('isCondensed', false)} + maxRows={select('maxRows', rows, 1)} hasError={boolean('hasError', false)} hasWarning={boolean('hasWarning', false)} value={value} @@ -50,6 +59,16 @@ storiesOf('Components|Inputs', module) action('onChange')(event); onChange(event.target.value); }} + rightActionIcon={ + boolean('hasRightAction') && + createElement( + icons[select('rightActionIcon', iconNames, iconNames[0])] + ) + } + rightActionProps={{ + label: 'Right action', + onClick: action('rightAction onClick'), + }} /> )} /> diff --git a/packages/components/inputs/multiline-text-input/src/multiline-text-input.styles.ts b/packages/components/inputs/multiline-text-input/src/multiline-text-input.styles.ts new file mode 100644 index 0000000000..db24fee5ff --- /dev/null +++ b/packages/components/inputs/multiline-text-input/src/multiline-text-input.styles.ts @@ -0,0 +1,27 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import { designTokens } from '@commercetools-uikit/design-system'; +import { type TMultilineTextInputProps } from './multiline-text-input'; + +const getMultilineTextInputActionIconStyles = ( + props: TMultilineTextInputProps +) => css` + position: absolute; + right: ${designTokens.spacing30}; + top: 0; + height: ${props.isCondensed + ? `${designTokens.heightForInputAsSmall}` + : `${designTokens.heightForInput}`}; + padding: 0; + display: flex; + justify-content: center; + align-items: center; +`; + +const MultilineInputWrapper = styled.div` + position: relative; + display: inline-block; + width: 100%; +`; + +export { getMultilineTextInputActionIconStyles, MultilineInputWrapper }; diff --git a/packages/components/inputs/multiline-text-input/src/multiline-text-input.tsx b/packages/components/inputs/multiline-text-input/src/multiline-text-input.tsx index d5aa6b64ea..feafb99505 100644 --- a/packages/components/inputs/multiline-text-input/src/multiline-text-input.tsx +++ b/packages/components/inputs/multiline-text-input/src/multiline-text-input.tsx @@ -3,19 +3,28 @@ import { useCallback, type ChangeEventHandler, type FocusEventHandler, + ReactElement, } from 'react'; +import SecondaryIconButton, { + type TSecondaryButtonIconProps, +} from '@commercetools-uikit/secondary-icon-button'; import { useIntl } from 'react-intl'; import { css } from '@emotion/react'; import { AngleUpIcon, AngleDownIcon } from '@commercetools-uikit/icons'; import FlatButton from '@commercetools-uikit/flat-button'; import { useToggleState } from '@commercetools-uikit/hooks'; -import { filterDataAttributes } from '@commercetools-uikit/utils'; +import { filterDataAttributes, warning } from '@commercetools-uikit/utils'; import Stack from '@commercetools-uikit/spacings-stack'; import Constraints from '@commercetools-uikit/constraints'; +import { designTokens } from '@commercetools-uikit/design-system'; import { MultilineInput, messagesMultilineInput, } from '@commercetools-uikit/input-utils'; +import { + MultilineInputWrapper, + getMultilineTextInputActionIconStyles, +} from './multiline-text-input.styles'; export type TMultilineTextInputProps = { /** @@ -99,6 +108,27 @@ export type TMultilineTextInputProps = { | 16 | 'scale' | 'auto'; + + /** + * Custom action icon to be displayed on the right side of the input. + */ + rightActionIcon?: ReactElement; + /** + * Props for the right-action icon-button. Required when rightActionIcon is provided. + * At least a `label` and an `onClick` prop/function need to be provided. + */ + rightActionProps?: TSecondaryButtonIconProps; + /** + * Set this to `true` to reduce the paddings of the input allowing the input to display + * more data in less space. + * + */ + isCondensed?: boolean; + /** + * Set this to value to determine the maximum text rows of the text area. + * Any text overflow past this row number would implement a scroll + */ + maxRows?: number; }; const defaultProps: Pick< @@ -135,30 +165,58 @@ const MultilineTextInput = (props: TMultilineTextInputProps) => { [setShouldRenderToggleButton] ); + if (props.rightActionIcon && !props.rightActionProps) { + warning( + false, + 'SelectableSearchInput: `rightActionIcon` is provided but `rightActionProps` is missing. Provide an object with a `label` and `onClick` property.' + ); + } + return ( - + + + {props.rightActionIcon && props.rightActionProps && ( +
+ +
+ )} +
{shouldRenderToggleButton && (
( hasWarning={true} /> + + {}} + horizontalConstraint={7} + rightActionIcon={} + rightActionProps={{ + label: 'Click me', + onClick: () => {}, + }} + /> + + + {}} + horizontalConstraint={7} + isCondensed={true} + /> + + + {}} + horizontalConstraint={7} + rightActionIcon={} + isCondensed={true} + rightActionProps={{ + label: 'Click me', + onClick: () => {}, + }} + /> + + + {}} + horizontalConstraint={7} + maxRows={3} + rightActionIcon={} + rightActionProps={{ + label: 'Click me', + onClick: () => {}, + }} + /> + {/*