diff --git a/packages/react-core/src/components/Alert/examples/AlertGroupAsync.tsx b/packages/react-core/src/components/Alert/examples/AlertGroupAsync.tsx index aa1f4f785c7..30f944c2fff 100644 --- a/packages/react-core/src/components/Alert/examples/AlertGroupAsync.tsx +++ b/packages/react-core/src/components/Alert/examples/AlertGroupAsync.tsx @@ -6,6 +6,7 @@ import { AlertActionCloseButton, AlertVariant, InputGroup, + InputGroupItem, useInterval } from '@patternfly/react-core'; @@ -45,12 +46,16 @@ export const AlertGroupAsync: React.FunctionComponent = () => { return ( - - + + + + + + {alerts.map(({ title, variant, key }) => ( diff --git a/packages/react-core/src/components/Alert/examples/AlertGroupMultipleDynamic.tsx b/packages/react-core/src/components/Alert/examples/AlertGroupMultipleDynamic.tsx index e001739dfe8..8e0058ac254 100644 --- a/packages/react-core/src/components/Alert/examples/AlertGroupMultipleDynamic.tsx +++ b/packages/react-core/src/components/Alert/examples/AlertGroupMultipleDynamic.tsx @@ -5,7 +5,8 @@ import { AlertGroup, AlertActionCloseButton, AlertVariant, - InputGroup + InputGroup, + InputGroupItem } from '@patternfly/react-core'; export const AlertGroupMultipleDynamic: React.FunctionComponent = () => { @@ -34,9 +35,11 @@ export const AlertGroupMultipleDynamic: React.FunctionComponent = () => { return ( - + + + {alerts.map(({ title, variant, key }) => ( diff --git a/packages/react-core/src/components/Alert/examples/AlertGroupSingularDynamic.tsx b/packages/react-core/src/components/Alert/examples/AlertGroupSingularDynamic.tsx index c38ec95b3a7..cbb80001cbb 100644 --- a/packages/react-core/src/components/Alert/examples/AlertGroupSingularDynamic.tsx +++ b/packages/react-core/src/components/Alert/examples/AlertGroupSingularDynamic.tsx @@ -5,7 +5,8 @@ import { AlertGroup, AlertActionCloseButton, AlertVariant, - InputGroup + InputGroup, + InputGroupItem } from '@patternfly/react-core'; export const AlertGroupSingularDynamic: React.FunctionComponent = () => { @@ -38,15 +39,21 @@ export const AlertGroupSingularDynamic: React.FunctionComponent = () => { return ( - - - + + + + + + + + + {alerts.map(({ title, variant, key }) => ( diff --git a/packages/react-core/src/components/Alert/examples/AlertGroupSingularDynamicOverflow.tsx b/packages/react-core/src/components/Alert/examples/AlertGroupSingularDynamicOverflow.tsx index 0ac8b045b90..a7597b23dbe 100644 --- a/packages/react-core/src/components/Alert/examples/AlertGroupSingularDynamicOverflow.tsx +++ b/packages/react-core/src/components/Alert/examples/AlertGroupSingularDynamicOverflow.tsx @@ -5,7 +5,8 @@ import { AlertGroup, AlertActionCloseButton, AlertVariant, - InputGroup + InputGroup, + InputGroupItem } from '@patternfly/react-core'; export const AlertGroupSingularDynamicOverflow: React.FunctionComponent = () => { @@ -57,15 +58,21 @@ export const AlertGroupSingularDynamicOverflow: React.FunctionComponent = () => return ( - - - + + + + + + + + + {alerts.slice(0, maxDisplayed).map(({ key, variant, title }) => ( diff --git a/packages/react-core/src/components/Alert/examples/AlertGroupToast.tsx b/packages/react-core/src/components/Alert/examples/AlertGroupToast.tsx index 59ce250fcc4..4df6acbf915 100644 --- a/packages/react-core/src/components/Alert/examples/AlertGroupToast.tsx +++ b/packages/react-core/src/components/Alert/examples/AlertGroupToast.tsx @@ -5,7 +5,8 @@ import { AlertGroup, AlertActionCloseButton, AlertVariant, - InputGroup + InputGroup, + InputGroupItem } from '@patternfly/react-core'; export const AlertGroupToast: React.FunctionComponent = () => { @@ -38,15 +39,21 @@ export const AlertGroupToast: React.FunctionComponent = () => { return ( - - - + + + + + + + + + {alerts.map(({ key, variant, title }) => ( diff --git a/packages/react-core/src/components/Alert/examples/AlertGroupToastOverflowCapture.tsx b/packages/react-core/src/components/Alert/examples/AlertGroupToastOverflowCapture.tsx index 2ea68546777..7e1beb8826c 100644 --- a/packages/react-core/src/components/Alert/examples/AlertGroupToastOverflowCapture.tsx +++ b/packages/react-core/src/components/Alert/examples/AlertGroupToastOverflowCapture.tsx @@ -5,7 +5,8 @@ import { AlertGroup, AlertActionCloseButton, AlertVariant, - InputGroup + InputGroup, + InputGroupItem } from '@patternfly/react-core'; export const AlertGroupToastOverflowCapture: React.FunctionComponent = () => { @@ -57,15 +58,21 @@ export const AlertGroupToastOverflowCapture: React.FunctionComponent = () => { return ( - - - + + + + + + + + + {alerts.slice(0, maxDisplayed).map(({ key, variant, title }) => ( diff --git a/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx index 33361b3f763..1a14fd9c15f 100644 --- a/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx @@ -3,7 +3,7 @@ import { TextInput } from '../TextInput'; import { Button } from '../Button'; import { Select, SelectList, SelectOption } from '../Select'; import { MenuToggle, MenuToggleElement } from '../MenuToggle'; -import { InputGroup } from '../InputGroup'; +import { InputGroup, InputGroupItem } from '../InputGroup'; import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon'; import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon'; import { css } from '@patternfly/react-styles'; @@ -259,67 +259,71 @@ export const CalendarMonth = ({ -
- - ) => ( + setIsSelectOpen(!isSelectOpen)} + isExpanded={isSelectOpen} + style={{ width: '140px' } as React.CSSProperties} + > + {monthFormatted} + + )} + aria-labelledby={hiddenMonthId} + isOpen={isSelectOpen} + onOpenChange={(isOpen) => { + setIsSelectOpen(isOpen); + onSelectToggle(isOpen); + }} + onSelect={(ev, monthNum) => { + // When we put CalendarMonth in a Popover we want the Popover's onDocumentClick + // to see the SelectOption as a child so it doesn't close the Popover. + setTimeout(() => { + setIsSelectOpen(false); + onSelectToggle(false); + const newDate = new Date(focusedDate); + newDate.setMonth(Number(monthNum as string)); + setFocusedDate(newDate); + setHoveredDate(newDate); + setShouldFocus(false); + onMonthChange(ev, newDate); + }, 0); + }} + selected={monthFormatted} + > + + {longMonths.map((longMonth, index) => ( + + {longMonth} + + ))} + + +
+ + +
+ , year: string) => { const newDate = new Date(focusedDate); - newDate.setMonth(Number(monthNum as string)); + newDate.setFullYear(+year); setFocusedDate(newDate); setHoveredDate(newDate); setShouldFocus(false); onMonthChange(ev, newDate); - }, 0); - }} - selected={monthFormatted} - > - - {longMonths.map((longMonth, index) => ( - - {longMonth} - - ))} - - -
-
- , year: string) => { - const newDate = new Date(focusedDate); - newDate.setFullYear(+year); - setFocusedDate(newDate); - setHoveredDate(newDate); - setShouldFocus(false); - onMonthChange(ev, newDate); - }} - /> -
+ }} + /> + +
+ + + + + +
diff --git a/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap b/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap index 1159611cf5f..70cb26056b6 100644 --- a/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap +++ b/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap @@ -12,34 +12,42 @@ exports[`With popover opened 1`] = `
- -
+
- - + + +
- -
- + +
- +
+ +
- -
+
+ + + +
diff --git a/packages/react-core/src/components/FileUpload/FileUploadField.tsx b/packages/react-core/src/components/FileUpload/FileUploadField.tsx index 6763ca9ad9e..13e673b9fd1 100644 --- a/packages/react-core/src/components/FileUpload/FileUploadField.tsx +++ b/packages/react-core/src/components/FileUpload/FileUploadField.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import styles from '@patternfly/react-styles/css/components/FileUpload/file-upload'; import { css } from '@patternfly/react-styles'; -import { InputGroup } from '../InputGroup'; +import { InputGroup, InputGroupItem } from '../InputGroup'; import { TextInput } from '../TextInput'; import { Button, ButtonVariant } from '../Button'; import { TextArea, TextAreResizeOrientation } from '../TextArea'; @@ -130,31 +130,37 @@ export const FileUploadField: React.FunctionComponent = ({ >
- - - + + + + + + + + +
diff --git a/packages/react-core/src/components/FileUpload/__tests__/__snapshots__/FileUpload.test.tsx.snap b/packages/react-core/src/components/FileUpload/__tests__/__snapshots__/FileUpload.test.tsx.snap index 2f6a9699033..820d283acf9 100644 --- a/packages/react-core/src/components/FileUpload/__tests__/__snapshots__/FileUpload.test.tsx.snap +++ b/packages/react-core/src/components/FileUpload/__tests__/__snapshots__/FileUpload.test.tsx.snap @@ -12,43 +12,55 @@ exports[`simple fileupload 1`] = `
- - -
+
- Clear - + +
+
+ +
- - -
+
- Clear - + +
+
+ +
= ({ innerRef, ...props }: InputGroupProps) => { - const formCtrls = [FormSelect, TextArea, TextInput].map((comp) => comp.displayName); - const idItem = React.Children.toArray(children).find( - (child: any) => !formCtrls.includes(child.type.displayName) && child.props.id - ) as React.ReactElement<{ id: string }>; - + const getIdItem = () => { + const getChildId = (_children: any) => + React.Children.toArray(_children).find( + (_child: any) => !formCtrls.includes(_child?.type?.displayName) && _child?.props?.id + ); + let childId = getChildId(children); + if (childId) { + return childId; + } + React.Children.toArray(children).find((child: any) => { + const _childId = getChildId(child.props.children); + if (_childId) { + childId = _childId; + return true; + } + }); + return childId; + }; + const formCtrls = [FormSelect, TextArea, TextInput].map(comp => comp.displayName); + const idItem = getIdItem() as React.ReactElement<{ id: string }>; const ref = React.useRef(null); const inputGroupRef = innerRef || ref; + const childrenWithId = React.Children.map(children, (child: any) => { + if (child?.type.displayName === 'InputGroupItem') { + const newChildren = React.Children.map(child.props.children, _child => { + if (!_child.props) { + return _child; + } + if (_child.props['aria-describedby']) { + return _child; + } + if (!formCtrls.includes(_child.type.displayName)) { + return _child; + } + return React.cloneElement(_child, { + 'aria-describedby': _child.props['aria-describedby'] === '' ? undefined : idItem?.props?.id + }); + }); + return React.cloneElement(child, {}, newChildren); + } + + if (child?.props['aria-describedby']) { + return child; + } + if (!formCtrls.includes(child?.type.displayName)) { + return child; + } + return React.cloneElement(child, { + 'aria-describedby': child.props['aria-describedby'] === '' ? undefined : idItem?.props?.id + }); + }); return (
- {idItem - ? React.Children.map(children, (child: any) => - !formCtrls.includes(child.type.displayName) || child.props['aria-describedby'] - ? child - : React.cloneElement(child, { - 'aria-describedby': child.props['aria-describedby'] === '' ? undefined : idItem.props.id - }) - ) - : children} + {idItem ? childrenWithId : children}
); }; diff --git a/packages/react-core/src/components/InputGroup/InputGroupItem.tsx b/packages/react-core/src/components/InputGroup/InputGroupItem.tsx new file mode 100644 index 00000000000..f3d4ebb205c --- /dev/null +++ b/packages/react-core/src/components/InputGroup/InputGroupItem.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import styles from '@patternfly/react-styles/css/components/InputGroup/input-group'; +import { css } from '@patternfly/react-styles'; + +export interface InputGroupItemProps extends React.HTMLProps { + /** Additional classes added to the input group item. */ + className?: string; + /** Content rendered inside the input group item. */ + children: React.ReactNode; + /** Enables box styling to the input group item */ + isBox?: boolean; + /** Flag to indicate if the input group item is plain. */ + isPlain?: boolean; + /** Flag to indicate if the input group item should fill the available horizontal space */ + isFill?: boolean; + /** Flag to indicate if the input group item is disabled. */ + isDisabled?: boolean; + +} + +export const InputGroupItem: React.FunctionComponent = ({ + className = '', + children, + isFill = false, + isBox = false, + isPlain, + isDisabled, + ...props +}: InputGroupItemProps) => ( +
+ {children} +
+); +InputGroupItem.displayName = 'InputGroupItem'; diff --git a/packages/react-core/src/components/InputGroup/__tests__/InputGroup.test.tsx b/packages/react-core/src/components/InputGroup/__tests__/InputGroup.test.tsx index d91a03091fd..cca2d2bf250 100644 --- a/packages/react-core/src/components/InputGroup/__tests__/InputGroup.test.tsx +++ b/packages/react-core/src/components/InputGroup/__tests__/InputGroup.test.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { InputGroup } from '../InputGroup'; +import { InputGroupItem } from '../InputGroupItem'; import { Button } from '../../Button'; import { TextInput } from '../../TextInput'; @@ -12,10 +13,14 @@ describe('InputGroup', () => { // If Button has an id props, this should be used in aria-describedby. render( - - + + + + + + ); expect(screen.getByLabelText('some text')).toHaveAttribute('aria-describedby', 'button-id'); @@ -27,10 +32,14 @@ describe('InputGroup', () => { // example has an empty aria-describedby to prevent that from happening. render( - - + + + + + + ); expect(screen.getByLabelText('some text')).not.toHaveAttribute('aria-describedby'); @@ -42,10 +51,14 @@ describe('InputGroup', () => { // example has a predefined aria-describedby to prevent that from happening render( - - + + + + + + ); expect(screen.getByLabelText('some text')).toHaveAttribute('aria-describedby', 'myself'); diff --git a/packages/react-core/src/components/InputGroup/examples/InputGroup.md b/packages/react-core/src/components/InputGroup/examples/InputGroup.md index 51a1bb722fc..5095fe18168 100644 --- a/packages/react-core/src/components/InputGroup/examples/InputGroup.md +++ b/packages/react-core/src/components/InputGroup/examples/InputGroup.md @@ -2,7 +2,7 @@ id: Input group section: components cssPrefix: null -propComponents: ['InputGroup', 'InputGroupText'] +propComponents: ['InputGroup', 'InputGroupItem', 'InputGroupText'] --- import AtIcon from '@patternfly/react-icons/dist/esm/icons/at-icon'; @@ -32,6 +32,7 @@ import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question- ### With popover +using `appendTo` with ref to the `InputGroup` for the Popover so that the popover width will depend on the whole input group ```ts file ='./InputGroupWithPopover.tsx' ``` diff --git a/packages/react-core/src/components/InputGroup/examples/InputGroupBasic.tsx b/packages/react-core/src/components/InputGroup/examples/InputGroupBasic.tsx index 83781408052..71a9264bfcd 100644 --- a/packages/react-core/src/components/InputGroup/examples/InputGroupBasic.tsx +++ b/packages/react-core/src/components/InputGroup/examples/InputGroupBasic.tsx @@ -1,31 +1,50 @@ import React from 'react'; import AtIcon from '@patternfly/react-icons/dist/esm/icons/at-icon'; -import { InputGroup, InputGroupText, InputGroupTextVariant, TextInput, ValidatedOptions } from '@patternfly/react-core'; +import { + InputGroup, + InputGroupText, + InputGroupItem, + InputGroupTextVariant, + TextInput, + ValidatedOptions +} from '@patternfly/react-core'; export const InputGroupBasic: React.FunctionComponent = () => ( - - @example.com + + + + + @example.com +
- - - - + + + + + + + +
- - - % - + + + + + + % + +
); diff --git a/packages/react-core/src/components/InputGroup/examples/InputGroupWithDropdown.tsx b/packages/react-core/src/components/InputGroup/examples/InputGroupWithDropdown.tsx index 1e0e6ebb67b..87acc8e9168 100644 --- a/packages/react-core/src/components/InputGroup/examples/InputGroupWithDropdown.tsx +++ b/packages/react-core/src/components/InputGroup/examples/InputGroupWithDropdown.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Button, InputGroup, + InputGroupItem, TextInput, Dropdown, DropdownList, @@ -24,32 +25,38 @@ export const InputGroupWithDropdown: React.FunctionComponent = () => { return ( - setIsOpen(isOpen)} - toggle={(toggleRef: React.Ref) => ( - - Dropdown - - )} - > - - - Option 1 - - - Option 2 - - - Option 3 - - - - - + + setIsOpen(isOpen)} + toggle={(toggleRef: React.Ref) => ( + + Dropdown + + )} + > + + + Option 1 + + + Option 2 + + + Option 3 + + + + + + + + + + ); diff --git a/packages/react-core/src/components/InputGroup/examples/InputGroupWithPopover.tsx b/packages/react-core/src/components/InputGroup/examples/InputGroupWithPopover.tsx index 9792812849f..5af8d261e6b 100644 --- a/packages/react-core/src/components/InputGroup/examples/InputGroupWithPopover.tsx +++ b/packages/react-core/src/components/InputGroup/examples/InputGroupWithPopover.tsx @@ -1,43 +1,57 @@ import React from 'react'; import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; -import { Button, InputGroup, TextInput, Popover, PopoverPosition } from '@patternfly/react-core'; +import { Button, InputGroup, InputGroupItem, TextInput, Popover, PopoverPosition } from '@patternfly/react-core'; -export const InputGroupWithPopover: React.FunctionComponent = () => ( - - - - - - - -
- - - - - - -
-); +export const InputGroupWithPopover: React.FunctionComponent = () => { + const inputGroupRef1 = React.useRef(null); + const inputGroupRef2 = React.useRef(null); + return ( + + + + + + + inputGroupRef1.current} + > + + + + +
+ + + + + + inputGroupRef2.current} + > + + + + +
+ ); +}; diff --git a/packages/react-core/src/components/InputGroup/examples/InputGroupWithSiblings.tsx b/packages/react-core/src/components/InputGroup/examples/InputGroupWithSiblings.tsx index 7b9ebad0ee1..213d19c2bef 100644 --- a/packages/react-core/src/components/InputGroup/examples/InputGroupWithSiblings.tsx +++ b/packages/react-core/src/components/InputGroup/examples/InputGroupWithSiblings.tsx @@ -1,37 +1,72 @@ import React from 'react'; import DollarSignIcon from '@patternfly/react-icons/dist/esm/icons/dollar-sign-icon'; -import { Button, TextArea, InputGroup, InputGroupText, TextInput } from '@patternfly/react-core'; +import { + Button, + TextArea, + InputGroup, + InputGroupText, + InputGroupItem, + TextInput, +} from '@patternfly/react-core'; export const InputGroupWithSiblings: React.FunctionComponent = () => ( - -