diff --git a/packages/react-components/src/components/ActionBar/ActionBar.tsx b/packages/react-components/src/components/ActionBar/ActionBar.tsx index f8cb01552..afac62116 100644 --- a/packages/react-components/src/components/ActionBar/ActionBar.tsx +++ b/packages/react-components/src/components/ActionBar/ActionBar.tsx @@ -135,6 +135,7 @@ export const ActionBar: React.FC = ({ )} triggerRenderer={ -
-
dispatch({ type: 'reference1' })} - id="reference1" - className="guide-reference" - > - Example reference 1 +
+ +
+
+
dispatch({ type: 'reference1' })} + id="reference1" + className="guide-reference" + > + Example reference 1 +
+
+
+
dispatch({ type: 'reference2' })} + id="reference2" + className="guide-reference" + > + Example reference 2 +
+
+
+
dispatch({ type: 'reference3' })} + id="reference3" + className="guide-reference" + > + Example reference 3 +
+
+ + + {state.reference === 'reference1' ? ( + dispatch({ type: 'reference2' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + {state.reference === 'reference2' ? ( + dispatch({ type: 'reference3' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + {state.reference === 'reference3' ? ( + dispatch({ type: 'isVisible' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} +
-
dispatch({ type: 'reference2' })} - id="reference2" - className="guide-reference" - > - Example reference 2 -
- -
dispatch({ type: 'reference3' })} - id="reference3" - className="guide-reference" - > - Example reference 3 -
- - - {state.reference === 'reference1' && } - {state.reference === 'reference2' && } - {state.reference === 'reference3' && } -
-
-); + ); +}; ``` +The above example only shows the way of implementation, state management is on the implementation side. +You can manage it in any way, the most important is to place in `UserGuide` an element that is to be +displayed next to the element the guide points to. We suggest using `UserGuideStep`. + +## Style highlighted element
+ +Component allows to easly style the highlighted element, by passing own styles properties. To do this, you need to enchant the `UserGuide` component with styles object using the `elementStyles` prop: + +```tsx +... + if (action.type === 'reference2') { + return { + ...state, + reference: 'reference2', + cursorPosition: 'right-start', + cursorTiming: 'moderate2', + elementStyles: { + // styles properties here + } + }; + } +... +``` + ## Component API diff --git a/packages/react-components/src/components/UserGuide/UserGuide.module.scss b/packages/react-components/src/components/UserGuide/UserGuide.module.scss index 452735eeb..ab1ece2fb 100644 --- a/packages/react-components/src/components/UserGuide/UserGuide.module.scss +++ b/packages/react-components/src/components/UserGuide/UserGuide.module.scss @@ -7,20 +7,11 @@ $radius: var(--radius-3); $arrow-size: 45px; .#{$base-class} { - z-index: $stacking-context-level-tooltip; - border: 1px solid var(--tooltip-border); - border-radius: $radius; - box-shadow: var(--shadow-tooltip); - background-color: var(--tooltip-background-basic); - padding: var(--spacing-3); - max-width: 320px; - color: var(--content-basic-primary); - font-size: inherit; - &__overlay { position: fixed; inset: 0; background-color: rgb(19 19 23 / 50%); + z-index: $stacking-context-level-tooltip; } &__floating { @@ -28,9 +19,11 @@ $arrow-size: 45px; transition-property: top, bottom, left, right; transition-timing-function: ease-in-out; + z-index: $stacking-context-level-tooltip; } &__guide { + display: flex; position: relative; &__arrow { @@ -44,148 +37,87 @@ $arrow-size: 45px; transition-timing-function: ease-in-out; &--right-start { - top: calc(0px - $arrow-size + #{$arrow-size / 2}); + top: 9px; + transform: rotate(-50deg); } &--right { top: calc(50% - #{$arrow-size / 2}); - transform: rotate(-45deg); + transform: rotate(-90deg); } &--right-end { - top: calc(100% - #{$arrow-size / 2}); - transform: rotate(-90deg); + top: calc(100% - 45px - 9px); + transform: rotate(-130deg) } &--bottom-start, &--bottom, &--bottom-end { - top: calc(0px - #{$arrow-size}); + top: 0px; } &--bottom-start { - left: calc(0px + #{$arrow-size} - #{$arrow-size / 2}); + left: 9px; + transform: rotate(-40deg); } &--bottom { left: calc(50% - #{$arrow-size / 2}); - transform: rotate(45deg); + transform: rotate(0deg); } &--bottom-end { - left: calc(100% - #{$arrow-size} - #{$arrow-size / 2}); - transform: rotate(90deg); + left: calc(100% - #{$arrow-size} - 9px); + transform: rotate(40deg); } &--top-start, &--top, &--top-end { - top: 100%; + top: calc(100% - #{$arrow-size}); } &--top-start { - left: calc(0px + #{$arrow-size} - #{$arrow-size / 2}); - transform: rotate(-90deg); + left: 9px; + transform: rotate(-140deg); } &--top { left: calc(50% - #{$arrow-size / 2}); - transform: rotate(-135deg); + transform: rotate(-180deg); } &--top-end { - left: calc(100% - #{$arrow-size} - #{$arrow-size / 2}); - transform: rotate(-180deg); + left: calc(100% - #{$arrow-size} - 9px); + transform: rotate(140deg); } &--left-start, &--left, &--left-end { - left: calc(100% - $arrow-size); + left: calc(100% - 43px); } &--left-start { - top: calc(0px - $arrow-size + #{$arrow-size / 2}); - transform: rotate(90deg); + top: 9px; + transform: rotate(50deg); } &--left { top: calc(50% - #{$arrow-size / 2}); left: calc(100% - $arrow-size); - transform: rotate(135deg); + transform: rotate(90deg); } &--left-end { - top: calc(100% - #{$arrow-size / 2}); - transform: rotate(180deg); + top: calc(100% - 45px - 9px); + transform: rotate(130deg); } } &__content { - @include transitions.durations(); - - transition-property: margin; - transition-timing-function: ease-in-out; - opacity: 0; - margin: 0; - animation: var(--transition-duration-moderate-1) fade-in - var(--transition-duration-moderate-2) forwards; - - &--right-start, - &--right, - &--right-end { - margin-left: $arrow-size; - } - - &--right-start { - margin-top: $arrow-size; - } - - &--right-end { - margin-bottom: $arrow-size; - } - - &--bottom-start, - &--bottom, - &--bottom-end { - margin-top: $arrow-size; - } - - &--bottom-start { - margin-left: $arrow-size; - } - - &--bottom-end { - margin-right: $arrow-size; - } - - &--top-start, - &--top, - &--top-end { - margin-bottom: $arrow-size; - } - - &--top-start { - margin-left: $arrow-size; - } - - &--top-end { - margin-right: $arrow-size; - } - - &--left-start, - &--left, - &--left-end { - margin-right: $arrow-size; - } - - &--left-start { - margin-top: $arrow-size; - } - - &--left-end { - margin-bottom: $arrow-size; - } + margin: 42px; } } @@ -215,3 +147,7 @@ $arrow-size: 45px; opacity: 1; } } + +:global(.user-guide-visible) { + overflow: hidden !important; +} diff --git a/packages/react-components/src/components/UserGuide/UserGuide.stories.tsx b/packages/react-components/src/components/UserGuide/UserGuide.stories.tsx index 4f4f225cb..b04d3f479 100644 --- a/packages/react-components/src/components/UserGuide/UserGuide.stories.tsx +++ b/packages/react-components/src/components/UserGuide/UserGuide.stories.tsx @@ -1,16 +1,32 @@ -import { ReactElement, useReducer } from 'react'; +import { CSSProperties, ReactElement, useReducer, useState } from 'react'; import { Placement } from '@floating-ui/react'; +import * as Icons from '@livechat/design-system-icons'; import { Meta } from '@storybook/react'; +import { AppFrame } from '../AppFrame'; +import { + Navigation, + NavigationItem, + NavigationGroup, + ExpirationCounter, + MobileNavigation, +} from '../AppFrame/components'; +import { ExampleTopBar, getArchivesSubMenu, getBadgeContent, getChatsMenu, getEngageSubMenu } from '../AppFrame/stories-helpers'; +import { Avatar } from '../Avatar'; import { Button } from '../Button'; -import './UserGuide.stories.css'; +import { Icon } from '../Icon'; +import { ProductSwitcher, useProductSwitcher } from '../ProductSwitcher'; +import { Tooltip } from '../Tooltip'; import { UserGuideStep } from './components/UserGuideStep'; import beautifulImage from './placeholder.png'; +import { AppContent } from './stories-helpers'; import { CursorTiming } from './types'; import { UserGuide } from './UserGuide'; +import './UserGuide.stories.css'; + export default { title: 'Components/UserGuide', component: UserGuide, @@ -25,7 +41,9 @@ export default { parameters: { controls: { expanded: true }, chromatic: { delay: 300 }, + layout: 'fullscreen' }, + subcomponents: { UserGuideStep }, } as Meta; export const Default = (): ReactElement => { @@ -43,7 +61,7 @@ export const Default = (): ReactElement => { ...state, reference: 'reference1', cursorPosition: 'left', - cursorTiming: 'fast1', + cursorTiming: 'moderate2', }; } if (action.type === 'reference2') { @@ -59,7 +77,7 @@ export const Default = (): ReactElement => { ...state, reference: 'reference3', cursorPosition: 'left-end', - cursorTiming: 'fast2', + cursorTiming: 'moderate2', }; } if (action.type === 'isVisible') { @@ -67,7 +85,7 @@ export const Default = (): ReactElement => { reference: 'reference1', isVisible: !state.isVisible, cursorPosition: 'left', - cursorTiming: 'fast1', + cursorTiming: 'moderate2', }; } @@ -78,7 +96,7 @@ export const Default = (): ReactElement => { reference: 'reference1', isVisible: false, cursorPosition: 'left', - cursorTiming: 'fast1', + cursorTiming: 'moderate2', }); return ( @@ -171,3 +189,401 @@ export const Default = (): ReactElement => {
); }; + +const navigationItems = [ + 'home', + 'chats', + 'engage', + 'archives', + 'tickets', + 'team', + 'reports', + 'apps', + 'billing', + 'settings', + 'news', +]; +const navigationItemsIcons = [ + Icons.LiveChatMono, + Icons.Messages, + Icons.Automation, + Icons.Archives, + Icons.Tickets, + Icons.People, + Icons.Report, + Icons.Apps, +]; +const secondaryNavigationIcons = [ + Icons.CreditCardOutline, + Icons.Settings, + Icons.Notifications, +]; + +const defaultImage = + 'https://cdn-labs.livechat-files.com/api/file/lc/img/100019504/df59da4b5b0cdb6030efb08787fd255d.jpg'; + +export const Example = (): ReactElement => { + const [activeItem, setActiveItem] = useState('archives'); + const [activeSubItem, setActiveSubItem] = useState(0); + const [topBarVisible, setTopBarVisible] = useState(true); + const [visibleAlert, setVisibleAlert] = useState(0); + + const { products } = useProductSwitcher({ + env: 'labs', + installedProducts: [ + { + product: 'ChatBot', + }, + { + product: 'HelpDesk', + }, + { + product: 'KnowledgeBase', + }, + { + product: 'LiveChat', + }, + { + product: 'OpenWidget', + }, + ], + organizationId: 'organizationId', + subscriptions: { + livechat: { status: 'active' }, + chatbot: { status: 'expired' }, + }, + mainProductId: 'livechat', + }); + + const getSubNav = () => { + switch (activeItem) { + case 'chats': + return getChatsMenu(activeSubItem, setActiveSubItem); + case 'engage': + return getEngageSubMenu(activeSubItem, setActiveSubItem); + case 'archives': + return getArchivesSubMenu(activeSubItem, setActiveSubItem); + default: + return null; + } + }; + + const reducer = ( + state: { + isVisible: boolean; + reference: string; + cursorPosition?: string; + cursorTiming?: string; + elementStyles?: CSSProperties; + }, + action: { type: string } + ) => { + if (action.type === 'home') { + return { + ...state, + isVisible: !state.isVisible, + reference: 'home', + cursorPosition: 'right-start', + cursorTiming: 'moderate2', + }; + } + if (action.type === 'archives') { + return { + ...state, + reference: 'archives', + cursorPosition: 'right-start', + cursorTiming: 'moderate2', + }; + } + if (action.type === 'user') { + return { + ...state, + reference: 'user', + cursorPosition: 'right-end', + cursorTiming: 'moderate2', + }; + } + if (action.type === 'chat-list-column') { + return { + ...state, + reference: 'chat-list-column', + cursorPosition: 'right', + cursorTiming: 'moderate2', + }; + } + if (action.type === 'text-area') { + return { + ...state, + reference: 'text-area', + cursorPosition: 'top', + cursorTiming: 'moderate2', + }; + } + if (action.type === 'action-bar-area') { + return { + ...state, + reference: 'action-bar-area', + cursorPosition: 'left', + cursorTiming: 'moderate2', + }; + } + if (action.type === 'one') { + return { + ...state, + reference: 'one', + cursorPosition: 'bottom-end', + cursorTiming: 'moderate2', + }; + } + if (action.type === 'action-bar-area-menu-button') { + return { + ...state, + reference: 'action-bar-area-menu-button', + cursorPosition: 'bottom-end', + cursorTiming: 'moderate2', + }; + } + if (action.type === 'accordion') { + return { + ...state, + reference: 'accordion', + cursorPosition: 'left', + cursorTiming: 'moderate2', + }; + } + if (action.type === 'isVisible') { + return { + reference: 'home', + isVisible: !state.isVisible, + cursorPosition: 'right-start', + cursorTiming: 'moderate2', + }; + } + + return state; + }; + + const [state, dispatch] = useReducer(reducer, { + reference: 'home', + isVisible: false, + cursorPosition: 'right-start', + cursorTiming: 'moderate2', + }); + + return ( + <> + + +
  • + +
  • + {navigationItems.slice(0, 8).map((item, index) => ( + } + onClick={(e, id) => { + e.preventDefault(); + setActiveItem(id); + }} + isActive={activeItem === item} + badge={getBadgeContent(item)} + /> + ))} +
    + + { + e.preventDefault(); + setActiveItem(id); + }} + /> + {navigationItems.slice(8, 11).map((item, index) => ( + } + onClick={(e, id) => { + e.preventDefault(); + setActiveItem(id); + }} + isActive={activeItem === item} + /> + ))} + + } + > + Custom element with own tooltip (native nav tooltip is + disabled) + + } + onClick={(e) => e.preventDefault()} + /> + + + } + mobileNavigation={ + + {navigationItems.slice(0, 5).map((item, index) => ( + } + onClick={(e, id) => { + e.preventDefault(); + setActiveItem(id); + }} + isActive={activeItem === item} + badge={getBadgeContent(item)} + /> + ))} + + } + sideNavigation={getSubNav()} + topBar={ + topBarVisible ? ( + + ) : null + } + > + dispatch({ type: 'home' })} /> +
    + + {state.reference === 'home' ? ( + dispatch({ type: 'archives' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + {state.reference === 'archives' ? ( + dispatch({ type: 'user' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + {state.reference === 'user' ? ( + dispatch({ type: 'chat-list-column' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + {state.reference === 'chat-list-column' ? ( + dispatch({ type: 'text-area' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + {state.reference === 'text-area' ? ( + dispatch({ type: 'action-bar-area' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + {state.reference === 'action-bar-area' ? ( + dispatch({ type: 'one' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + {state.reference === 'one' ? ( + dispatch({ type: 'action-bar-area-menu-button' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + {state.reference === 'action-bar-area-menu-button' ? ( + dispatch({ type: 'accordion' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + {state.reference === 'accordion' ? ( + dispatch({ type: 'isVisible' })} + handleCloseAction={() => dispatch({ type: 'isVisible' })} + /> + ) : null} + + + ) +} diff --git a/packages/react-components/src/components/UserGuide/UserGuide.tsx b/packages/react-components/src/components/UserGuide/UserGuide.tsx index cb9fe8cd7..b8dad94ab 100644 --- a/packages/react-components/src/components/UserGuide/UserGuide.tsx +++ b/packages/react-components/src/components/UserGuide/UserGuide.tsx @@ -4,6 +4,8 @@ import { FloatingPortal } from '@floating-ui/react'; import { autoUpdate, flip, shift, useFloating } from '@floating-ui/react-dom'; import cx from 'clsx'; +import cursor from '../../stories/assets/cursor.svg'; + import { IUserGuide } from './types'; import VirtualReference from './virtualElementReference'; @@ -24,7 +26,6 @@ export const UserGuide: FC> = ({ elementStyles, }) => { const [parentElement, setParentElement] = useState(null); - const [wasVisible, setWasVisible] = useState(false); const [rect, setRect] = useState(null); const containerRef = useRef(null); const contentRef = useRef(null); @@ -94,10 +95,16 @@ export const UserGuide: FC> = ({ }, [parentElement, containerRef.current, isVisible]); useEffect(() => { - if (wasVisible && isVisible) { - setWasVisible(false); + if (isVisible) { + document.body.classList.add('user-guide-visible'); + } else { + document.body.classList.remove('user-guide-visible'); } - }, [children]); + + return () => { + document.body.classList.remove('user-guide-visible'); + }; + }, [isVisible]); const cloneReferenceElement = () => { if (!rect) return null; @@ -150,113 +157,31 @@ export const UserGuide: FC> = ({ styles[`${baseClass}__guide__arrow--${cursorTiming}`] )} > - - - - - - + {/* + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + */} + cursor {children && (
    void; - handleCloseAction?: (ev: KeyboardEvent | MouseEvent) => void; -}> = ({ +export const UserGuideStep: FC = ({ header, text, image, + video, currentStep, stepMax, handleCloseAction, @@ -58,6 +50,16 @@ export const UserGuideStep: FC<{ />
    )} + {video && !image && ( +