From da04d2343a7c5ff0926ff16f3522dee7a6dd558c Mon Sep 17 00:00:00 2001 From: Nandan Devadula <47176249+devadula-nandan@users.noreply.github.com> Date: Wed, 17 Jul 2024 21:20:06 +0530 Subject: [PATCH] refactor(Coachmark): implement popover and floating ui (#5574) * refactor(Coachmark): implement popover and floating ui * chore: cleanup * fix: focus issue and tests * chore: cleanup * fix: merge conflicts * fix: build ts issue * fix: positioning when this is used beside any element * fix: positionTune, and few position issues * chore: removed unwanted comments --- .../Coachmark/_coachmark-overlay.scss | 76 ++------------- .../src/components/Coachmark/_coachmark.scss | 9 +- .../CoachmarkBeacon/_coachmark-beacon.scss | 4 +- .../Coachmark/Coachmark.stories.jsx | 26 ++++- .../src/components/Coachmark/Coachmark.tsx | 97 ++++++++++++++----- .../components/Coachmark/CoachmarkOverlay.tsx | 3 +- .../CoachmarkBeacon/CoachmarkBeacon.tsx | 5 - 7 files changed, 110 insertions(+), 110 deletions(-) diff --git a/packages/ibm-products-styles/src/components/Coachmark/_coachmark-overlay.scss b/packages/ibm-products-styles/src/components/Coachmark/_coachmark-overlay.scss index 708b4da58e..b6d51f79d3 100644 --- a/packages/ibm-products-styles/src/components/Coachmark/_coachmark-overlay.scss +++ b/packages/ibm-products-styles/src/components/Coachmark/_coachmark-overlay.scss @@ -45,6 +45,11 @@ $draghandle-btn-class: #{$block-class}__handle; visibility: hidden; + &--tooltip { + position: initial; + transform: none !important; + } + &--fixed { position: fixed; right: $spacing-05; @@ -68,45 +73,19 @@ $draghandle-btn-class: #{$block-class}__handle; visibility: visible; } - // CARET STYLING - &__caret { - // background-color: this property is set in _coachmark-overlay-theme.scss - position: absolute; - z-index: 5902; - width: 0; - height: 0; - // border-bottom-color: this property is set in _coachmark-overlay-theme.scss - border-right: $caret-center solid transparent; - border-bottom: solid $caret-center $background-inverse; - border-left: $caret-center solid transparent; - } - - // OVERLAY AND CARET POSITIONING + // OVERLAY POSITIONING &--top { $horizontal-push: calc($distance-offset - $caret-center); $translate-y: calc(-1 * (100% + $distance-offset + $caret-height)); transform: translate(-50%, $translate-y); - .#{$block-class}__caret { - left: calc(50% - $caret-center); - transform: rotate(180deg); - } - &-left { transform: translate(calc(-1 * $distance-offset), $translate-y); - .#{$block-class}__caret { - left: $horizontal-push; - transform: rotate(180deg); - } } &-right { transform: translate(calc(-1 * (100% - $distance-offset)), $translate-y); - .#{$block-class}__caret { - right: $horizontal-push; - transform: rotate(180deg); - } } } @@ -116,26 +95,13 @@ $draghandle-btn-class: #{$block-class}__handle; $translate-y: calc($distance-offset + $caret-height); transform: translate(-50%, $translate-y); - .#{$block-class}__caret { - top: $top; - left: calc(50% - $caret-center); - } &-left { transform: translate(calc(-1 * $distance-offset), $translate-y); - .#{$block-class}__caret { - top: $top; - left: $horizontal-push; - } } &-right { transform: translate(calc(-1 * (100% - $distance-offset)), $translate-y); - - .#{$block-class}__caret { - top: $top; - right: $horizontal-push; - } } } @@ -145,28 +111,13 @@ $draghandle-btn-class: #{$block-class}__handle; $vertical-push: calc($distance-offset - ($caret-height * 0.5)); transform: translate($translate-x, -50%); - .#{$block-class}__caret { - top: calc(50% - ($caret-height * 0.5)); - right: $right; - transform: rotate(90deg); - } &-top { transform: translate($translate-x, calc(-1 * $distance-offset)); - .#{$block-class}__caret { - top: $vertical-push; - right: $right; - transform: rotate(90deg); - } } &-bottom { transform: translate($translate-x, calc(-1 * (100% - $distance-offset))); - .#{$block-class}__caret { - right: $right; - bottom: $vertical-push; - transform: rotate(90deg); - } } } @@ -176,28 +127,13 @@ $draghandle-btn-class: #{$block-class}__handle; $vertical-push: calc($distance-offset - ($caret-height * 0.5)); transform: translate($translate-x, -50%); - .#{$block-class}__caret { - top: calc(50% - ($caret-height * 0.5)); - left: $left; - transform: rotate(-90deg); - } &-top { transform: translate($translate-x, calc(-1 * $distance-offset)); - .#{$block-class}__caret { - top: $vertical-push; - left: $left; - transform: rotate(-90deg); - } } &-bottom { transform: translate($translate-x, calc(-1 * (100% - $distance-offset))); - .#{$block-class}__caret { - bottom: $vertical-push; - left: $left; - transform: rotate(-90deg); - } } } diff --git a/packages/ibm-products-styles/src/components/Coachmark/_coachmark.scss b/packages/ibm-products-styles/src/components/Coachmark/_coachmark.scss index 9281f5a0f4..a63a47d294 100644 --- a/packages/ibm-products-styles/src/components/Coachmark/_coachmark.scss +++ b/packages/ibm-products-styles/src/components/Coachmark/_coachmark.scss @@ -8,6 +8,7 @@ // Standard imports. @use '../../global/styles/project-settings' as c4p-settings; @use '../../global/styles/mixins'; +@use '@carbon/styles/scss/config'; @use './coachmark-dragbar'; @use './coachmark-header'; @@ -23,8 +24,8 @@ // TODO: @use(s) of IBM Products component styles used by Coachmark // The block part of our conventional BEM class names (blockClass__E--M). -//$block-class: #{c4p-settings.$pkg-prefix}--coachmark; +$block-class: #{c4p-settings.$pkg-prefix}--coachmark; -// .#{$block-class} { -// // TODO: Styles. -// } +.#{$block-class} { + position: relative; +} diff --git a/packages/ibm-products-styles/src/components/CoachmarkBeacon/_coachmark-beacon.scss b/packages/ibm-products-styles/src/components/CoachmarkBeacon/_coachmark-beacon.scss index 70d34859a8..764d6e4210 100644 --- a/packages/ibm-products-styles/src/components/CoachmarkBeacon/_coachmark-beacon.scss +++ b/packages/ibm-products-styles/src/components/CoachmarkBeacon/_coachmark-beacon.scss @@ -69,9 +69,7 @@ $block-class: #{c4p-settings.$pkg-prefix}--coachmark-beacon; } .#{$block-class}__target { // the hit area - position: absolute; - top: calc(-1 * $spacing-05); - left: calc(-1 * $spacing-05); + display: flex; width: $spacing-07; height: $spacing-07; padding: 0; diff --git a/packages/ibm-products/src/components/Coachmark/Coachmark.stories.jsx b/packages/ibm-products/src/components/Coachmark/Coachmark.stories.jsx index 3cc43f77dd..3bdd587e2b 100644 --- a/packages/ibm-products/src/components/Coachmark/Coachmark.stories.jsx +++ b/packages/ibm-products/src/components/Coachmark/Coachmark.stories.jsx @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; // TODO: import action to handle events if required. // import { action } from '@storybook/addon-actions'; import { Crossroads } from '@carbon/react/icons'; @@ -87,8 +87,13 @@ export default { * TODO: Declare template(s) for one or more scenarios. */ const Template = (args) => { + const ref = useRef(); + + useEffect(() => { + ref?.current?.scrollIntoView({ block: 'center', inline: 'center' }); + }); const theme = getSelectedCarbonTheme(); - return ( + const content = ( { ); + + return !['fixed', 'floating', 'stacked'].includes(args.overlayKind) ? ( +
+
+ {content} +
+
+ ) : ( + content + ); }; /** diff --git a/packages/ibm-products/src/components/Coachmark/Coachmark.tsx b/packages/ibm-products/src/components/Coachmark/Coachmark.tsx index 8395b0bb7b..40271c20f9 100644 --- a/packages/ibm-products/src/components/Coachmark/Coachmark.tsx +++ b/packages/ibm-products/src/components/Coachmark/Coachmark.tsx @@ -24,7 +24,10 @@ import { useClickOutsideElement, useWindowEvent } from './utils/hooks'; import { getDevtoolsProps } from '../../global/js/utils/devtools'; import { pkg /*, carbon */ } from '../../settings'; import { throttle } from 'lodash'; +/**@ts-ignore */ +import { Popover, PopoverAlignment, PopoverContent } from '@carbon/react'; import { useIsomorphicEffect } from '../../global/js/hooks'; + // The block part of our conventional BEM class names (blockClass__E--M). const blockClass = `${pkg.prefix}--coachmark`; const overlayBlockClass = `${blockClass}-overlay`; @@ -56,7 +59,11 @@ interface CoachmarkProps { | 'top' | 'top-left' | 'top-right'; - + /** + * Auto aligns the coachmark based on screen boundaries + * Applies only to Tooltip Coachmarks. + */ + autoAlign?: boolean; /** * Coachmark should use a single CoachmarkOverlayElements component as a child. * @see CoachmarkOverlayElements @@ -82,7 +89,6 @@ interface CoachmarkProps { overlayKind?: 'tooltip' | 'floating' | 'stacked'; overlayRef?: MutableRefObject; - /** * By default, the Coachmark will be appended to the end of `document.body`. * The Coachmark will remain persistent as the user navigates the app until @@ -94,10 +100,11 @@ interface CoachmarkProps { * element is hidden or component is unmounted, the Coachmark will disappear. */ portalTarget?: string; + /** * Fine tune the position of the target in pixels. Applies only to Beacons. */ - positionTune?: { x: number; y: number } | object; + positionTune?: { x: number; y: number }; /** * The optional button or beacon that the user will click to show the Coachmark. */ @@ -118,14 +125,15 @@ export let Coachmark = forwardRef( ( { align = defaults.align, + autoAlign, children, className, onClose = defaults.onClose, overlayClassName, overlayKind = defaults.overlayKind, overlayRef, - portalTarget, positionTune, + portalTarget, target, theme = defaults.theme, @@ -199,6 +207,10 @@ export let Coachmark = forwardRef( setShouldResetPosition(true); } }; + const overlayPositionStyle = { + top: (positionTune?.y ?? 0) - 16, + left: (positionTune?.x ?? 0) - 16, + }; const contextValue = { buttonProps: { @@ -216,6 +228,7 @@ export let Coachmark = forwardRef( targetOffset: targetOffset, align: align, positionTune: positionTune, + isOpen: isOpen, }; const handleResize = throttle(() => { closeOverlay(); @@ -259,26 +272,57 @@ export let Coachmark = forwardRef( } {...getDevtoolsProps(componentName)} > - {target} - {isOpen && - portalNode?.current && - createPortal( - } - fixedIsVisible={false} - kind={overlayKind} - onClose={handleClose} - theme={theme} - className={cx( - overlayClassName, - `${overlayBlockClass}--is-visible` + {overlayKind !== 'tooltip' ? ( + <> + {target} + {isOpen && + portalNode?.current && + createPortal( + } + fixedIsVisible={false} + kind={overlayKind} + onClose={handleClose} + theme={theme} + className={cx( + overlayClassName, + `${overlayBlockClass}--is-visible` + )} + > + {children} + , + // Default to `document.body` when `portalNode` is `null` + portalNode?.current + )} + + ) : ( + + {target} + + {isOpen && ( + } + fixedIsVisible={false} + kind={overlayKind} + onClose={handleClose} + theme={theme} + className={cx(overlayClassName, { + [`${overlayBlockClass}--is-visible`]: isOpen, + })} + > + {children} + )} - > - {children} - , - // Default to `document.body` when `portalNode` is `null` - portalNode?.current - )} + + + )} ); @@ -321,6 +365,11 @@ Coachmark.propTypes = { 'top-left', 'top-right', ]), + /** + * Auto aligns the coachmark based on screen boundaries + * Applies only to Tooltip Coachmarks. + */ + autoAlign: PropTypes.bool, /** * Coachmark should use a single CoachmarkOverlayElements component as a child. @@ -349,7 +398,6 @@ Coachmark.propTypes = { overlayRef: PropTypes.shape({ current: overlayRefType as PropTypes.Validator, }), - /** * By default, the Coachmark will be appended to the end of `document.body`. * The Coachmark will remain persistent as the user navigates the app until @@ -364,6 +412,7 @@ Coachmark.propTypes = { /** * Fine tune the position of the target in pixels. Applies only to Beacons. */ + // @ts-ignore positionTune: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number, diff --git a/packages/ibm-products/src/components/Coachmark/CoachmarkOverlay.tsx b/packages/ibm-products/src/components/Coachmark/CoachmarkOverlay.tsx index e9917e9597..9877a4b82f 100644 --- a/packages/ibm-products/src/components/Coachmark/CoachmarkOverlay.tsx +++ b/packages/ibm-products/src/components/Coachmark/CoachmarkOverlay.tsx @@ -99,7 +99,7 @@ export let CoachmarkOverlay = forwardRef( const coachmark = useCoachmark(); const isBeacon = kind === COACHMARK_OVERLAY_KIND.TOOLTIP; const isDraggable = kind === COACHMARK_OVERLAY_KIND.FLOATING; - const isVisible = className && className.includes('is-visible'); + const isVisible = className?.includes('is-visible'); const handleKeyPress = (event) => { const { shiftKey, key } = event; @@ -223,7 +223,6 @@ export let CoachmarkOverlay = forwardRef( }); })} - {isBeacon && } ); } diff --git a/packages/ibm-products/src/components/CoachmarkBeacon/CoachmarkBeacon.tsx b/packages/ibm-products/src/components/CoachmarkBeacon/CoachmarkBeacon.tsx index c20a1ac917..9ab810624d 100644 --- a/packages/ibm-products/src/components/CoachmarkBeacon/CoachmarkBeacon.tsx +++ b/packages/ibm-products/src/components/CoachmarkBeacon/CoachmarkBeacon.tsx @@ -57,10 +57,6 @@ export let CoachmarkBeacon = React.forwardRef< ); } - const overlayPositionStyle = { - top: coachmark.positionTune?.y ?? 0, - left: coachmark.positionTune?.x ?? 0, - }; return (