diff --git a/.storybook/main.js b/.storybook/main.js index 93a78946b..90589d332 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -15,7 +15,7 @@ const config = { titlePrefix: 'Components', files: '**/*.stories.[tj]s' }], - + staticDirs: ['../guides/static'], addons: [ '@storybook/addon-essentials', '@storybook/addon-mdx-gfm' diff --git a/CHANGELOG.md b/CHANGELOG.md index 07629cf6b..cdefd9bf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ * Default the scope of `` to `document.body`. Remove internal wrapping div. Refs STCOM-1351. * Remove inline styling from `` content. Refs STCOM-1348. * Use ``'s formatter for rendering the selected value within the field and prevent overflow of text. Refs STCOM-1344. +* Export `` that will automatically apply the `usePortal` behavior to nested overlay components. Refs STCOM-1353. ## [12.1.0](https://github.com/folio-org/stripes-components/tree/v12.1.0) (2024-03-12) [Full Changelog](https://github.com/folio-org/stripes-components/compare/v12.0.0...v12.1.0) diff --git a/guides/UIModuleLayout.stories.mdx b/guides/UIModuleLayout.stories.mdx index b6ddfc9f2..a4b9c6f81 100644 --- a/guides/UIModuleLayout.stories.mdx +++ b/guides/UIModuleLayout.stories.mdx @@ -1,4 +1,6 @@ import { Meta } from '@storybook/addon-docs'; +import usePortalFalse from './static/use-portal-false.gif'; +import usePortalTrue from './static/use-portal-true.gif'; @@ -42,3 +44,29 @@ class LayoutExample extends React.Component { export default LayoutExample; ``` + +## Portals + +Another detail of the stripes UI is that we render a special element that 'overlay' components such as Dropdowns or Datepicker's Calendar control can render into if the controls run the risk of +being cut off by the border of a containing element. This image shows an example of this problem - controls in the list are cut off by the list's wrapping element. + +Problematic overlay control, cut off by the border of its container. + +Each of these overlay components implements a `usePortal` prop that will render its menu to the special overlay element. We also export a `` that will automatically apply +the `usePortal` behavior to nested overlay components. `` and `` components build in a `` by default. + +Overlay control with issue resolve via the 'usePortal' prop. + +This feature should **only** be used when overlapping is possible since it does involve rendering interactive elements/sending focus to other parts of the DOM. + +Basic Usage: + +``` +{/* usePortal is necessary for individual components outside of a StripesOverlayWrapper */} + + + + {/* usePortal isn't necessary here */} + + +``` diff --git a/guides/static/use-portal-false.gif b/guides/static/use-portal-false.gif new file mode 100644 index 000000000..e25ce6f85 Binary files /dev/null and b/guides/static/use-portal-false.gif differ diff --git a/guides/static/use-portal-true.gif b/guides/static/use-portal-true.gif new file mode 100644 index 000000000..f5a99b8e8 Binary files /dev/null and b/guides/static/use-portal-true.gif differ diff --git a/index.js b/index.js index 62872c202..1bde5a251 100644 --- a/index.js +++ b/index.js @@ -171,7 +171,7 @@ export { currenciesByNumber, currenciesOptions } from './util/currencies'; - +export { default as StripesOverlayWrapper } from './util/StripesOverlayWrapper'; export { default as countries, countriesByCode, diff --git a/lib/AutoSuggest/readme.md b/lib/AutoSuggest/readme.md index 0e937249e..1c7734bbb 100644 --- a/lib/AutoSuggest/readme.md +++ b/lib/AutoSuggest/readme.md @@ -17,9 +17,7 @@ prop | description | default | required `onChange` | Callback called when the value changes | | `renderOption` | Callback that renders the item in the dropdown | `item => item.value` | `renderValue` | Callback that render the item in the input field | `item => item.value` | -`usePortal` | bool | If `true`, suggestion list will render to the `div[#OverlayContainer]` element in the FOLIO UI. | | +`usePortal` | bool | If `true`, option list will render to the `div[#OverlayContainer]` element in the FOLIO UI. Given the container of this component, `usePortal` may not be required. See [portals documentation](https://folio-org.github.io/stripes-components/iframe.html?viewMode=docs&id=guides-ui-layout--docs#portals) for guidance. | | `valueKey` | The key in the item object to use as the value. | `"value"` `withFinalForm` | toggle form time: true - final-form, false - redux-from (default) | | `popper` | object | Used to adjust placement of options list overlay via underlying Popper component. [See `` props](../Popper/readme.md) | | - - diff --git a/lib/Datepicker/readme.md b/lib/Datepicker/readme.md index 8d425754a..2951c87c6 100644 --- a/lib/Datepicker/readme.md +++ b/lib/Datepicker/readme.md @@ -31,7 +31,7 @@ Name | type | description | default | required `timeZone` | string | Overrides the time zone provided by context. | "UTC" | false `useFocus` | bool | if set to false, component relies solely on clicking the calendar icon to toggle appearance of calendar. | true | false `useInput` | bool | tells the Datepicker that it is being used under react-final-form, so it can modify its behaviour accodingly. **This is necessary when used with react-final-form** or the Datepicker will not work. | false | false -`usePortal` | bool | if true, the Datepicker will render itself to a React-Portal (the `#OverlayContainer` div) this avoids haveing the Datepicker cutoff by overflow. | false | false +`usePortal` | bool | if true, the Datepicker will render itself to a React-Portal (the `#OverlayContainer` div) this avoids having the Datepicker cut off by overflow. Given the container of this component, `usePortal` may not be required. See [portals documentation](https://folio-org.github.io/stripes-components/iframe.html?viewMode=docs&id=guides-ui-layout--docs#portals) for guidance. | false | false `value` | string | date to be displayed in the textfield. In forms, this is supplied by the initialValues prop supplied to the form | "" | false diff --git a/lib/Modal/WrappingElement.js b/lib/Modal/WrappingElement.js index 2eadd29ec..a9b436b99 100644 --- a/lib/Modal/WrappingElement.js +++ b/lib/Modal/WrappingElement.js @@ -8,7 +8,7 @@ import { import PropTypes from 'prop-types'; import * as focusTrap from 'focus-trap'; import { listen } from '../../util/listen'; -import StripesOverlayContext from '../../util/StripesOverlayContext'; +import StripesOverlayWrapper from '../../util/StripesOverlayWrapper'; import { OVERLAY_CONTAINER_SELECTOR } from '../../util/consts'; import calloutCSS from '../Callout/Callout.css'; import overlayCSS from '../Popper/Popper.css'; @@ -155,9 +155,9 @@ const WrappingElement = forwardRef(({ ref={wrappingElementRef} {...rest} > - + {children} - + ); }); diff --git a/lib/MultiColumnList/MCLRenderer.js b/lib/MultiColumnList/MCLRenderer.js index 92d0cbee4..f458df21e 100644 --- a/lib/MultiColumnList/MCLRenderer.js +++ b/lib/MultiColumnList/MCLRenderer.js @@ -18,7 +18,7 @@ import memoizeOne from 'memoize-one'; import Icon from '../Icon'; import EmptyMessage from '../EmptyMessage'; -import StripesOverlayContext from '../../util/StripesOverlayContext'; +import StripesOverlayWrapper from '../../util/StripesOverlayWrapper'; import { HotKeys } from '../HotKeys'; import SRStatus from '../SRStatus'; import css from './MCLRenderer.css'; @@ -1936,7 +1936,7 @@ class MCLRenderer extends React.Component { : css.mclRowContainer; return ( - +
@@ -2019,7 +2019,7 @@ class MCLRenderer extends React.Component { }
-
+
); } } diff --git a/lib/MultiSelection/readme.md b/lib/MultiSelection/readme.md index 0bc484799..240d16293 100644 --- a/lib/MultiSelection/readme.md +++ b/lib/MultiSelection/readme.md @@ -42,7 +42,7 @@ Name | type | description | default | required `onRemove` | func | Event handler specifically called when an item is removed from the selection. The removed item is passed to the handler. | | `placeholder` | string | Rendered as a placeholder for the control when no value is present. | | `showLoading` | bool | Should render loading indicator on the field | | -`usePortal` | bool | If `true`, option list will render to the `div[#OverlayContainer]` element in the FOLIO UI. | | +`usePortal` | bool | If `true`, option list will render to the `div[#OverlayContainer]` element in the FOLIO UI. Given the container of this component, `usePortal` may not be required. See [portals documentation](https://folio-org.github.io/stripes-components/iframe.html?viewMode=docs&id=guides-ui-layout--docs#portals) for guidance. | | `value` | array | Array of selected objects. | | `valueFormatter` | func | Render function that accepts an object with keys for the option. The function is called to display values in the selected values list. If the prop is missing, `formatter` will be used instead. | | `ariaLabelledBy` | string | Used for applying an accessible label if no `label` prop is provided | | diff --git a/lib/Selection/readme.md b/lib/Selection/readme.md index 789352209..e0c874d37 100644 --- a/lib/Selection/readme.md +++ b/lib/Selection/readme.md @@ -82,7 +82,7 @@ Name | type | description | default | required `useValidStyle` | bool | if true, "success" styles will be applied to control if it contains a valid value `onBlur` (using redux-form validation.) | false | `autoFocus` | bool | If this prop is `true`, control will automatically focus on mount | | `popper` | object | Used to adjust placement of options list overlay via underlying Popper component. [See `` props](../Popper/readme.md) | | false | -`usePortal` | bool | If `true`, option list will render to the `div[#OverlayContainer]` element in the FOLIO UI. | | +`usePortal` | bool | If `true`, option list will render to the `div[#OverlayContainer]` element in the FOLIO UI. Given the container of this component, `usePortal` may not be required. See [portals documentation](https://folio-org.github.io/stripes-components/iframe.html?viewMode=docs&id=guides-ui-layout--docs#portals) for guidance. | | ## Labeling Like other form controls in stripes-components, `` abides by standard conventions for labeling props if alternatives to `label` (visible label with the control) are required... `aria-label` and `aria-labelledby` are useful for this. See [Accessiblity for developers documentation](https://github.com/folio-org/stripes-components/blob/master/guides/AccessibilityDevPrimer.stories.mdx#labeling) for more details about which to choose. diff --git a/lib/Timepicker/readme.md b/lib/Timepicker/readme.md index 92f68c6d3..2ae9e19c8 100644 --- a/lib/Timepicker/readme.md +++ b/lib/Timepicker/readme.md @@ -46,7 +46,7 @@ Name | type | description | default | required `timeZone` | string | Overrides the time zone provided by context. | "UTC" | false `timeFormat` | string | String to override default time format according to locale | | false `useInput` | bool | If true - Outputs the value as it is displayed in the input. | false | -`usePortal` | bool | if true, the Timepicker will render itself to a React-Portal (the `#OverlayContainer` div) this avoids haveing the Timepicker cutoff by overflow. | false | false +`usePortal` | bool | if true, the Timepicker will render itself to a React-Portal (the `#OverlayContainer` div) this avoids having the Timepicker cut off by overflow. Given the container of this component, `usePortal` may not be required. See [portals documentation](https://folio-org.github.io/stripes-components/iframe.html?viewMode=docs&id=guides-ui-layout--docs#portals) for guidance. | false | false `value` | string | time to be displayed in the visible input. | | false diff --git a/util/StripesOverlayWrapper.js b/util/StripesOverlayWrapper.js new file mode 100644 index 000000000..cd7fe4002 --- /dev/null +++ b/util/StripesOverlayWrapper.js @@ -0,0 +1,13 @@ +import StripesOverlayContext from './StripesOverlayContext'; + +/** UI modules can import `StripesOverlayWrapper` to wrap component trees where +* usage of the `usePortal` prop will be prevalent among children. + + + +*/ +export default ({ children }) => ( + + {children} + +);