From b2bf1771fc5784c9eae74b7f708030ce35dcf9a5 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Thu, 23 Jul 2020 13:11:48 -0400 Subject: [PATCH] feat: allow renderProp pattern in OverlayTrigger (#5316) --- package.json | 4 +- src/OverlayTrigger.tsx | 74 +++++++------------ www/src/components/Heading.js | 15 +--- www/src/components/NavMain.js | 4 +- www/src/components/SideNav.js | 6 +- www/src/components/Toc.js | 6 +- www/src/examples/Overlays/OverlayTrigger.js | 19 ----- www/src/examples/Overlays/Trigger.js | 15 ++++ .../examples/Overlays/TriggerRenderProp.js | 21 ++++++ www/src/pages/components/overlays.js | 27 ++++++- yarn.lock | 16 ++-- 11 files changed, 107 insertions(+), 100 deletions(-) delete mode 100644 www/src/examples/Overlays/OverlayTrigger.js create mode 100644 www/src/examples/Overlays/Trigger.js create mode 100644 www/src/examples/Overlays/TriggerRenderProp.js diff --git a/package.json b/package.json index ac86f72144..7b627aba82 100644 --- a/package.json +++ b/package.json @@ -76,8 +76,8 @@ "invariant": "^2.2.4", "prop-types": "^15.7.2", "prop-types-extra": "^1.1.0", - "react-overlays": "^4.0.0", - "react-transition-group": "^4.0.0", + "react-overlays": "^4.1.0", + "react-transition-group": "^4.4.1", "uncontrollable": "^7.0.0", "warning": "^4.0.3" }, diff --git a/src/OverlayTrigger.tsx b/src/OverlayTrigger.tsx index 849fdf23a0..e3caacea71 100644 --- a/src/OverlayTrigger.tsx +++ b/src/OverlayTrigger.tsx @@ -5,7 +5,6 @@ import useTimeout from '@restart/hooks/useTimeout'; import safeFindDOMNode from 'react-overlays/safeFindDOMNode'; import warning from 'warning'; import { useUncontrolledProp } from 'uncontrollable'; -import { Modifier } from 'react-overlays/esm/usePopper'; import Overlay, { OverlayChildren, OverlayProps } from './Overlay'; export type OverlayTriggerType = 'hover' | 'click' | 'focus'; @@ -16,9 +15,15 @@ export type OverlayInjectedProps = { onFocus?: (...args: any[]) => any; }; +export type OverlayTriggerRenderProps = OverlayInjectedProps & { + ref: React.Ref; +}; + export interface OverlayTriggerProps extends Omit { - children: React.ReactElement; + children: + | React.ReactElement + | ((props: OverlayTriggerRenderProps) => React.ReactNode); trigger?: OverlayTriggerType | OverlayTriggerType[]; delay?: OverlayDelay; show?: boolean; @@ -51,9 +56,9 @@ function normalizeDelay(delay?: OverlayDelay) { // for cases when the trigger is disabled and mouseOut/Over can cause flicker // moving from one child element to another. function handleMouseOverOut( - handler: (...args: any[]) => any, + handler: (...args: [React.MouseEvent, ...any[]]) => any, args: [React.MouseEvent, ...any[]], - relatedNative, + relatedNative: 'fromElement' | 'toElement', ) { const [e] = args; const target = e.currentTarget; @@ -188,9 +193,10 @@ function OverlayTrigger({ const delay = normalizeDelay(propsDelay); - const child = React.Children.only(children); - - const { onFocus, onBlur, onClick } = child.props; + const { onFocus, onBlur, onClick } = + typeof children !== 'function' + ? React.Children.only(children).props + : ({} as any); const getTarget = useCallback( () => safeFindDOMNode(triggerNodeRef.current), @@ -228,7 +234,7 @@ function OverlayTrigger({ const handleFocus = useCallback( (...args: any[]) => { handleShow(); - if (onFocus) onFocus(...args); + onFocus?.(...args); }, [handleShow, onFocus], ); @@ -236,7 +242,7 @@ function OverlayTrigger({ const handleBlur = useCallback( (...args: any[]) => { handleHide(); - if (onBlur) onBlur(...args); + onBlur?.(...args); }, [handleHide, onBlur], ); @@ -263,34 +269,6 @@ function OverlayTrigger({ [handleHide], ); - // We add aria-describedby in the case where the overlay is a role="tooltip" - // for other cases describedby isn't appropriate (e.g. a popover with inputs) so we don't add it. - const ariaModifier: Modifier<'ariaDescribedBy', Record> = { - name: 'ariaDescribedBy', - enabled: true, - phase: 'afterWrite', - effect: ({ state }) => { - return () => { - if ('removeAttribute' in state.elements.reference) - state.elements.reference.removeAttribute('aria-describedby'); - }; - }, - fn: ({ state }) => { - const { popper, reference } = state.elements; - - if (!show || !reference) return; - - const role = popper.getAttribute('role') || ''; - if ( - popper.id && - role.toLowerCase() === 'tooltip' && - 'setAttribute' in reference - ) { - reference.setAttribute('aria-describedby', popper.id); - } - }, - }; - const triggers: string[] = trigger == null ? [] : [].concat(trigger as any); const triggerProps: any = {}; @@ -312,25 +290,23 @@ function OverlayTrigger({ triggerProps.onMouseOut = handleMouseOut; } - // TODO: fix typing - // @ts-ignore - const modifiers = [ariaModifier].concat(popperConfig.modifiers || []); return ( <> - - {cloneElement(child as any, triggerProps)} - + {typeof children === 'function' ? ( + children({ ...triggerProps, ref: triggerNodeRef }) + ) : ( + + {cloneElement(children as any, triggerProps)} + + )} {overlay} diff --git a/www/src/components/Heading.js b/www/src/components/Heading.js index e84a131275..590bb47cbd 100644 --- a/www/src/components/Heading.js +++ b/www/src/components/Heading.js @@ -10,18 +10,7 @@ const styles = css` composes: __heading from global; position: relative; - pointer-events: none; - - &:before { - display: block; - height: 6rem; - margin-top: -6rem; - visibility: hidden; - content: ''; - } - } - .inner { - pointer-events: auto; + scroll-margin-top: 5rem; } `; @@ -35,7 +24,7 @@ const Heading = ({ h, id, title, className, children, registerNode }) => { const H = `h${h}`; return ( -
{children}
+ {children}
); }; diff --git a/www/src/components/NavMain.js b/www/src/components/NavMain.js index 48cf6c08f7..bd2e586a15 100644 --- a/www/src/components/NavMain.js +++ b/www/src/components/NavMain.js @@ -32,8 +32,6 @@ const Banner = styled(Navbar).attrs({ } @include media-breakpoint-up(md) { - position: sticky; - top: 0rem; z-index: 1040; } `; @@ -51,7 +49,7 @@ const StyledNavbar = styled(Navbar).attrs({ @include media-breakpoint-up(md) { position: sticky; - top: 4rem; + top: 0rem; z-index: 1040; } `; diff --git a/www/src/components/SideNav.js b/www/src/components/SideNav.js index 8d0b564c2c..f1df14219c 100644 --- a/www/src/components/SideNav.js +++ b/www/src/components/SideNav.js @@ -18,15 +18,17 @@ const MenuButton = styled(Button).attrs({ variant: 'link' })` const SidePanel = styled('div')` @import '../css/theme'; + $top: 4rem; + composes: d-flex flex-column from global; background-color: #f7f7f7; @include media-breakpoint-up(md) { position: sticky; - top: 4rem; + top: $top; z-index: 1000; - height: calc(100vh - 4rem); + height: calc(100vh - #{$top}); background-color: #f7f7f7; border-right: 1px solid $divider; } diff --git a/www/src/components/Toc.js b/www/src/components/Toc.js index 3b6df3a84b..42ecb052cf 100644 --- a/www/src/components/Toc.js +++ b/www/src/components/Toc.js @@ -6,10 +6,12 @@ export const TocContext = React.createContext(); const SidePanel = styled('div')` @import '../css/theme'; + $top: 4rem; + order: 2; position: sticky; - top: 4rem; - height: calc(100vh - 4rem); + top: $top; + height: calc(100vh - #{$top}); padding-top: 1.5rem; padding-bottom: 1.5rem; font-size: 0.875rem; diff --git a/www/src/examples/Overlays/OverlayTrigger.js b/www/src/examples/Overlays/OverlayTrigger.js deleted file mode 100644 index cee3c9f162..0000000000 --- a/www/src/examples/Overlays/OverlayTrigger.js +++ /dev/null @@ -1,19 +0,0 @@ -function renderTooltip(props) { - return ( - - Simple tooltip - - ); -} - -const Example = () => ( - - - -); - -render(); diff --git a/www/src/examples/Overlays/Trigger.js b/www/src/examples/Overlays/Trigger.js new file mode 100644 index 0000000000..c79f5cf470 --- /dev/null +++ b/www/src/examples/Overlays/Trigger.js @@ -0,0 +1,15 @@ +const renderTooltip = (props) => ( + + Simple tooltip + +); + +render( + + + , +); diff --git a/www/src/examples/Overlays/TriggerRenderProp.js b/www/src/examples/Overlays/TriggerRenderProp.js new file mode 100644 index 0000000000..e0c6d3a4fb --- /dev/null +++ b/www/src/examples/Overlays/TriggerRenderProp.js @@ -0,0 +1,21 @@ +render( + Check out this avatar} + > + {({ ref, ...triggerHandler }) => ( + + )} + , +); diff --git a/www/src/pages/components/overlays.js b/www/src/pages/components/overlays.js index 58cfbd3b53..9806c2d194 100644 --- a/www/src/pages/components/overlays.js +++ b/www/src/pages/components/overlays.js @@ -5,10 +5,12 @@ import { css } from 'astroturf'; import LinkedHeading from '../../components/LinkedHeading'; import ComponentApi from '../../components/ComponentApi'; import ReactPlayground from '../../components/ReactPlayground'; +import Callout from '../../components/Callout'; import Disabled from '../../examples/Overlays/Disabled'; import Overlay from '../../examples/Overlays/Overlay'; -import OverlayTrigger from '../../examples/Overlays/OverlayTrigger'; +import OverlayTrigger from '../../examples/Overlays/Trigger'; +import TriggerRenderProp from '../../examples/Overlays/TriggerRenderProp'; import PopoverBasic from '../../examples/Overlays/PopoverBasic'; import PopoverContained from '../../examples/Overlays/PopoverContained'; import PopoverPositioned from '../../examples/Overlays/PopoverPositioned'; @@ -91,7 +93,7 @@ export default withLayout(function TooltipSection({ data }) {

- + OverlayTrigger

@@ -116,6 +118,27 @@ export default withLayout(function TooltipSection({ data }) { + + Customizing trigger behavior + + +

+ For more advanced behaviors {''} accepts a + function child that passes in the injected ref and event + handlers that coorespond to the configured trigger prop. +

+

+ You can manually apply the props to any element you want or split them + up. The example below shows how to position the overlay to a different + element than the one that triggers its visibility. +

+ + Pro Tip: Using the function form of OverlayTrigger + avoids a React.findDOMNode call, for those trying to be + strict mode compliant. + + + Tooltips diff --git a/yarn.lock b/yarn.lock index 9bd663185c..797efaa47d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7601,10 +7601,10 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-overlays@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-4.0.0.tgz#7fbcb60d12fee3733e9e4dd216b225e94fb3befe" - integrity sha512-LpznWocwgeB5oWKg6cDdkqKP7MbX4ClKbJqgZGUMXPRBBYcqrgM6TjjZ/8DeurNU//GuqwQMjhmo/JVma4XEWw== +react-overlays@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-4.1.0.tgz#755a890519b02e3904845172d5223ff2dfb1bb29" + integrity sha512-vdRpnKe0ckWOOD9uWdqykLUPHLPndIiUV7XfEKsi5008xiyHCfL8bxsx4LbMrfnxW1LzRthLyfy50XYRFNQqqw== dependencies: "@babel/runtime" "^7.4.5" "@popperjs/core" "^2.0.0" @@ -7625,10 +7625,10 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.13.1: react-is "^16.8.6" scheduler "^0.19.1" -react-transition-group@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683" - integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw== +react-transition-group@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" + integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== dependencies: "@babel/runtime" "^7.5.5" dom-helpers "^5.0.1"