Skip to content

Commit

Permalink
feat(UserGuide): new user guide with moving arrow
Browse files Browse the repository at this point in the history
  • Loading branch information
marcinsawicki committed Jan 22, 2025
1 parent 5882c54 commit 37f8c99
Show file tree
Hide file tree
Showing 16 changed files with 880 additions and 265 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export const ActionBar: React.FC<IActionBarProps> = ({
)}
triggerRenderer={
<Button
id={`${id}-menu-button`}
className={cx(
styles[`${menuWrapperClass}__button`],
buttonElement && styles[`${menuWrapperClass}__button--active`]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const ExpirationCounter: React.FC<IExpirationCounterProps> = ({
}) => (
<li key={id} className={cx(styles[baseClass], className)}>
<a
id={id}
tabIndex={0}
href={url}
onClick={(e) => onClick(e, id)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const NavigationItem: React.FC<INavigationItemProps> = ({

return (
<li
id={id}
key={id}
className={cx(
styles[baseClass],
Expand All @@ -77,6 +78,7 @@ export const NavigationItem: React.FC<INavigationItemProps> = ({
triggerRenderer={
<>
<a
// id={id}
tabIndex={disabled ? -1 : 0}
aria-label={label}
className={cx(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ export const NavigationTopBar = ({
children,
className,
additionalNodes,
id,
}: INavigationTopBarProps): React.ReactElement => {
const { isMobileViewEnabled } = useAppFrame();

return (
<div className={cx(styles[baseClass], className)}>
<div className={cx(styles[baseClass], className)} id={id}>
<div className={styles[`${baseClass}__alerts-wrapper`]}>{children}</div>
{!isMobileViewEnabled && additionalNodes}
</div>
Expand Down Expand Up @@ -95,6 +96,7 @@ export const NavigationTopBarAlert: React.FC<ITopBarAlertProps> = ({
primaryCta,
secondaryCta,
isVisible = true,
id,
}) => {
const [isMobile, setIsMobile] = React.useState<boolean>(false);
const alertRef = React.useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -178,6 +180,7 @@ export const NavigationTopBarAlert: React.FC<ITopBarAlertProps> = ({
role="status"
>
<div
id={id}
data-testid="navigation-top-bar-alert"
className={cx(
styles[alertClass],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import styles from './examples.module.scss';
export const DisconnectedAlert: React.FC<{
show: boolean;
onClose: () => void;
}> = ({ show, onClose }) => {
id?: string;
}> = ({ show, onClose, id }) => {
const [isReconnecting, setIsReconnecting] = React.useState(false);
const [isReconnected, setIsReconnected] = React.useState(false);

Expand All @@ -31,6 +32,7 @@ export const DisconnectedAlert: React.FC<{

return (
<NavigationTopBar.Alert
id={id}
kind={isReconnected ? 'success' : 'error'}
isVisible={show}
primaryCta={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ export const ExampleTopBar: React.FC<{
topBarVisible: boolean;
visibleAlert: number | null;
setVisibleAlert: (index: number | null) => void;
}> = ({ topBarVisible, visibleAlert, setVisibleAlert }) => {
id?: string;
}> = ({ topBarVisible, visibleAlert, setVisibleAlert, id }) => {
const [kind, setKind] = React.useState<
'info' | 'success' | 'warning' | 'error'
>('warning');
Expand Down Expand Up @@ -197,6 +198,7 @@ export const ExampleTopBar: React.FC<{
}
>
<DisconnectedAlert
id={id}
show={visibleAlert === 0}
onClose={() => closeAlert()}
/>
Expand Down
163 changes: 123 additions & 40 deletions packages/react-components/src/components/UserGuide/UserGuide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as Stories from './UserGuide.stories';

<Title>UserGuide</Title>

[Intro](#Intro) | [Usage](#Usage) | [Component API](#ComponentAPI) | [Content Spec](#ContentSpec)
[Intro](#Intro) | [Usage](#Usage) | [Style highlighted element](#style) | [Component API](#ComponentAPI) | [Content Spec](#ContentSpec)

## Intro <a id="Intro" />

Expand All @@ -21,6 +21,7 @@ A simple user guide that can be used to guide users through a series of steps.
```tsx
import {
UserGuide,
UserGuideStep,
Button,
} from '@livechat/design-system-react-components';

Expand All @@ -32,24 +33,32 @@ const reducer = (
return {
...state,
reference: 'reference1',
cursorPosition: 'left',
cursorTiming: 'moderate2',
};
}
if (action.type === 'reference2') {
return {
...state,
reference: 'reference2',
cursorPosition: 'right-start',
cursorTiming: 'moderate2',
};
}
if (action.type === 'reference3') {
return {
...state,
reference: 'reference3',
cursorPosition: 'left-end',
cursorTiming: 'moderate2',
};
}
if (action.type === 'isVisible') {
return {
reference: 'reference1',
isVisible: !state.isVisible,
cursorPosition: 'left',
cursorTiming: 'moderate2',
};
}

Expand All @@ -59,54 +68,128 @@ const reducer = (
const [state, dispatch] = React.useReducer(reducer, {
reference: 'reference1',
isVisible: false,
cursorPosition: 'left',
cursorTiming: 'moderate2',
});

return (
<div className="simple-user-guide-container">
<Button onClick={() => dispatch({ type: 'isVisible' })}>
Start guide
</Button>
<div className="guide-container">
<div
onClick={() => dispatch({ type: 'reference1' })}
id="reference1"
className="guide-reference"
>
Example reference 1
<div className="simple-user-guide-container">
<Button onClick={() => dispatch({ type: 'isVisible' })}>
Start guide
</Button>
<div style={{ display: 'flex' }}>
<div style={{ marginTop: 300 }}>
<div
onClick={() => dispatch({ type: 'reference1' })}
id="reference1"
className="guide-reference"
>
Example reference 1
</div>
</div>
<div style={{ marginTop: 50 }}>
<div
onClick={() => dispatch({ type: 'reference2' })}
id="reference2"
className="guide-reference"
>
Example reference 2
</div>
</div>
<div style={{ marginTop: 500 }}>
<div
onClick={() => dispatch({ type: 'reference3' })}
id="reference3"
className="guide-reference"
>
Example reference 3
</div>
</div>

<UserGuide
isVisible={state.isVisible}
parentElementName={`#${state.reference}`}
cursorPosition={state.cursorPosition as Placement}
cursorTiming={state.cursorTiming as CursorTiming}
zIndex={1000}
>
{state.reference === 'reference1' ? (
<UserGuideStep
header="Title text goes here"
text="Some text, maximum 210 characters. But can be divided into couple of message. More or less can be up to 4 lines. So let’s see how it looks like and let’s make it 4 lines. Ok, cool."
image={{
src: beautifulImage,
alt: 'image',
}}
currentStep={1}
stepMax={3}
handleClickPrimary={() => dispatch({ type: 'reference2' })}
handleCloseAction={() => dispatch({ type: 'isVisible' })}
/>
) : null}

{state.reference === 'reference2' ? (
<UserGuideStep
header="Title text goes here"
text="Some text, maximum 210 characters. But can be divided into couple of message. More or less can be up to 4 lines. So let’s see how it looks like and let’s make it 4 lines. Ok, cool."
image={{
src: beautifulImage,
alt: 'image',
}}
currentStep={2}
stepMax={3}
handleClickPrimary={() => dispatch({ type: 'reference3' })}
handleCloseAction={() => dispatch({ type: 'isVisible' })}
/>
) : null}

{state.reference === 'reference3' ? (
<UserGuideStep
header="Title text goes here"
text="Some text, maximum 210 characters. But can be divided into couple of message. More or less can be up to 4 lines. So let’s see how it looks like and let’s make it 4 lines. Ok, cool."
image={{
src: beautifulImage,
alt: 'image',
}}
currentStep={3}
stepMax={3}
handleClickPrimary={() => dispatch({ type: 'isVisible' })}
handleCloseAction={() => dispatch({ type: 'isVisible' })}
/>
) : null}
</UserGuide>
</div>
<div
onClick={() => dispatch({ type: 'reference2' })}
id="reference2"
className="guide-reference"
>
Example reference 2
</div>

<div
onClick={() => dispatch({ type: 'reference3' })}
id="reference3"
className="guide-reference"
>
Example reference 3
</div>

<UserGuide
isVisible={state.isVisible}
parentElementName={`#${state.reference}`}
zIndex={1000}
shouldSlide
>
{state.reference === 'reference1' && <Button onClick={() => dispatch({ type: 'reference2' })}>Next</Button>}
{state.reference === 'reference2' && <Button onClick={() => dispatch({ type: 'reference3' })}>Next</Button>}
{state.reference === 'reference3' && <Button onClick={() => dispatch({ type: 'isVisible' })}>Finish</Button>}
</UserGuide>
</div>
</div>
);
);
};
```

<Canvas of={Stories.Default} />

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 <a id="style" />

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 <a id="ComponentAPI" />

<ArgTypes of={Stories.Default} sort="requiredFirst" />
Expand Down
Loading

0 comments on commit 37f8c99

Please sign in to comment.