Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(OnboardingChecklist): new component #1270

Merged
merged 19 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3b9d8a0
feat(OnboardingChecklist): new component
marcinsawicki Aug 2, 2024
d63ef0b
feat(OnboardingChecklist): line decorator
marcinsawicki Aug 2, 2024
3c7e13e
feat(OnboardingChecklist): update
marcinsawicki Aug 3, 2024
d3600f5
feat(OnboardingChecklist): media query update
marcinsawicki Aug 3, 2024
777c1d6
feat(OnboardingChecklist): resize observer
marcinsawicki Aug 5, 2024
6a36f45
feat(OnboradingChecklist): complete state
marcinsawicki Aug 5, 2024
fd76eea
feat(OnboardingChecklist): change token background
marcinsawicki Aug 6, 2024
3727cb9
feat(OnboardingChecklist): icon for complete state
marcinsawicki Aug 6, 2024
d94e4af
feat(OnboardingChecklist): reopen after complete
marcinsawicki Aug 6, 2024
059d85c
feat(OnboardingChecklist): changes after review
marcinsawicki Aug 7, 2024
20cd0e2
feat(OnboardingChecklist): unit tests
marcinsawicki Aug 7, 2024
c848480
feat(OnboardingChecklist): ui update and focus statet management
marcinsawicki Aug 7, 2024
9dc4905
feat(OnboardingChecklist): update
marcinsawicki Aug 7, 2024
b986961
feat(OnboardingChecklist): update
marcinsawicki Aug 7, 2024
ea5b449
Merge branch 'main' into feature/onboarding-checklist
marcinsawicki Aug 7, 2024
5f1b22b
feat(OnboardingChecklist): focus state fix
marcinsawicki Aug 7, 2024
b267e49
Merge branch 'feature/onboarding-checklist' of github.com:livechat/de…
marcinsawicki Aug 7, 2024
7a46a08
feat(OnboardingChecklist): animation fix
marcinsawicki Aug 8, 2024
1d53e69
Merge branch 'main' into feature/onboarding-checklist
marcinsawicki Aug 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ArgTypes, Meta, Title, Canvas } from '@storybook/blocks';

import * as OnboardingChecklistStories from './OnboardingChecklist.stories';

<Meta of={OnboardingChecklistStories} />

<Title>Onboarding Checklist</Title>

[Intro](#Intro) | [Implementation example](#Implementation) | [Component API](#ComponentAPI)

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

`OnboardingChecklist` is a component used to create a kind of list of steps that are to guide the user through the application configuration process.
The left column is used to display a list of steps with descriptions and cta, while the right column is used to display a graphical presentation of a given step.

<Canvas of={OnboardingChecklistStories.Default} />

#### `OnboardingChecklist` implementation example <a id="Implementation" />

```jsx
import { OnboardingChecklist } from '@livechat/design-system-react-components';

const items = [
{
id: '1',
title: 'Run a test chat',
description: 'Make it match your brand and website',
placeholder: <img src="[url]" />,
cta: (
<>
<Button
kind="high-contrast"
icon={<Icon source={Icons.OpenInNew} />}
iconPosition="right"
onClick={() => {}}
>
Chat with yourself
</Button>
<Button kind="plain" onClick={() => {}}>
Skip
</Button>
</>
),
},
...
];
const checkedIds = ['1', '2'];

<OnboardingChecklist
title="Here’s your getting started guide"
greetingText="Hello, Tim!"
items={items}
activeId={'1'}
checkedId={checkedIds}
onActiveChange={(id) => {}}
isCompleted={false}
completeItem={{
title: 'Your getting started guide is completed. ',
greetingText: 'Hey Tim, nice work!',
placeholder: <img src="[url]" />,
}}
/>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
$base-class: 'onboarding-checklist';

.#{$base-class} {
display: flex;
position: relative;
flex-direction: row;
transition: all var(--transition-duration-moderate-1);
border-radius: var(--radius-4);
background-color: var(--surface-check-list-background);
padding: 0;
width: 100%;
max-width: 1280px;
overflow: hidden;

&--complete {
background-color: var(--surface-accent-emphasis-low-positive);
}

&__complete {
display: flex;
flex-direction: row;
gap: var(--spacing-5);
padding: var(--spacing-5);
width: 100%;

&__icon {
color: var(--surface-accent-emphasis-high-positive);
}

&__label {
margin: 0;
}

&__title {
margin: var(--spacing-2) 0 0;
}
}

&__column {
position: relative;
padding: var(--spacing-10);
width: 50%;

&--right {
background: var(--surface-gradient-05);
}
}

&__header {
display: flex;
flex-direction: column;
margin-bottom: var(--spacing-9);

&__label {
margin-bottom: 4px;
color: var(--content-basic-secondary);
}

&__title {
margin: 0;
}
}

&__checklist {
position: relative;
}

&__placeholder {
display: flex;
position: absolute;
inset: 0;
align-items: center;
justify-content: center;
transition: all var(--transition-duration-moderate-1);
}

&__button {
position: absolute;
top: 30px;
right: var(--spacing-5);

&__icon {
transition: all var(--transition-duration-moderate-1);

&--open {
transform: rotate(180deg);
}
}
}
}

@media (width <= 919px) {
.#{$base-class}__column {
width: 100%;

&--right {
display: none;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as React from 'react';

import userEvent from '@testing-library/user-event';

import { render, vi } from 'test-utils';

import { OnboardingChecklist } from './OnboardingChecklist';
import { IOnboardingChecklistProps } from './types';

const defaultItems = [
{
id: '1',
title: 'Item 1',
description: 'Description 1',
cta: <button>CTA 1</button>,
placeholder: <span>Placeholder 1</span>,
},
{
id: '2',
title: 'Item 2',
description: 'Description 2',
cta: <button>CTA 2</button>,
placeholder: <span>Placeholder 2</span>,
},
];

const defaultProps: IOnboardingChecklistProps = {
title: 'Title',
greetingText: 'Greeting',
items: defaultItems,
activeItemId: '1',
completedItemsIds: [],
onActiveChange: vi.fn(),
completionMessageData: {
title: 'Completion message',
greetingText: 'Completion greeting',
delay: 1,
},
};

const renderComponent = (props: IOnboardingChecklistProps) => {
return render(<OnboardingChecklist {...props} />);
};

describe('<OnboardingChecklist> component', () => {
it('should allow for custom class', () => {
const { container } = renderComponent({
...defaultProps,
className: 'custom-class',
});

expect(container.firstChild).toHaveClass('custom-class');
});

it('should render component with initial state based on given props', () => {
const { getByText, getByTestId } = renderComponent(defaultProps);
const item1 = getByTestId('checklist-item-1');
const item2 = getByTestId('checklist-item-2');

expect(getByText('Title')).toBeInTheDocument();
expect(getByText('Greeting')).toBeInTheDocument();
expect(getByText('Item 1')).toBeInTheDocument();
expect(getByText('Item 2')).toBeInTheDocument();
expect(item1).toHaveAttribute('aria-expanded', 'true');
expect(item2).toHaveAttribute('aria-expanded', 'false');
});

it('should call onActiveChange with correct item id after user click', () => {
const onActiveChange = vi.fn();
const { getByText } = renderComponent({
...defaultProps,
onActiveChange,
});

userEvent.click(getByText('Item 2'));
expect(onActiveChange).toHaveBeenCalledWith('2');
});

it('should render complete state when isCompleted is true', () => {
const { getByText, queryByText } = renderComponent({
...defaultProps,
isCompleted: true,
});

expect(queryByText('Title')).not.toBeInTheDocument();
expect(queryByText('Item 1')).not.toBeInTheDocument();
expect(queryByText('Item 2')).not.toBeInTheDocument();
expect(getByText('Completion message')).toBeInTheDocument();
expect(getByText('Completion greeting')).toBeInTheDocument();
});
});
Loading
Loading