console: add Hasura familiarity survey component

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5829
Co-authored-by: Abhijeet Khangarot <26903230+abhi40308@users.noreply.github.com>
GitOrigin-RevId: ca2ebd3c3ab15f9b3e75497517f6c0d11c8440b1
This commit is contained in:
nevermore 2022-09-12 21:00:38 +05:30 committed by hasura-bot
parent 00b1acaf38
commit cc6c026ca9
6 changed files with 253 additions and 0 deletions

View File

@ -0,0 +1,10 @@
import React from 'react';
import { ComponentMeta, Story } from '@storybook/react';
import { Root } from './HasuraFamiliaritySurvey';
export default {
title: 'features/HasuraFamiliaritySurvey/Root',
component: Root,
} as ComponentMeta<typeof Root>;
export const Base: Story = () => <Root onSkip={() => {}} />;

View File

@ -0,0 +1,83 @@
import React from 'react';
import { FaStar, FaHeart, FaUser, FaBookmark } from 'react-icons/fa';
import { Dialog } from '@/new-components/Dialog';
import { IconCardGroup } from '@/new-components/IconCardGroup';
type CustomDialogFooterProps = {
onSkip: () => void;
};
const CustomDialogFooter: React.FC<CustomDialogFooterProps> = props => {
const { onSkip } = props;
return (
<div className="flex justify-center border-t border-gray-300 bg-white p-sm">
<div className="flex">
<div className="ml-2">
<a
className="underline text-blue-600 cursor-pointer"
onClick={onSkip}
>
Skip
</a>
</div>
</div>
</div>
);
};
export type Props = {
// onSubmit: () => void;
onSkip: () => void;
};
// TODO: Subscribe this data object to db data using a custom hook for data fetching.
const data: {
value: string;
icon: React.ReactNode;
title: string;
body: string;
}[] = [
{
value: '1',
icon: <FaHeart className="text-yellow-500 text-xl" />,
title: 'New User',
body: `I'm completely new to Hasura`,
},
{
value: '2',
icon: <FaBookmark className="text-yellow-500 text-xl" />,
title: 'Past User',
body: `I've used Hasura before but not actively developing right now`,
},
{
value: '3',
icon: <FaUser className="text-yellow-500 text-xl" />,
title: 'Recurring User',
body: `I'm already using Hasura (CE/Cloud) weekly/monthly`,
},
{
value: '4',
icon: <FaStar className="text-yellow-500 text-xl" />,
title: 'Active User',
body: `I'm actively developing with Hasura (CE/Cloud) daily`,
},
];
export const Root: React.FC<Props> = ({ onSkip }) => {
return (
<Dialog
title="Welcome To Hasura!"
description={`We'd love to get to know you before you get started with your first API.`}
hasBackdrop
footer={<CustomDialogFooter onSkip={onSkip} />}
>
<div className="mx-2 px-2">
<div className="font-bold">How familiar are you with Hasura?</div>
<div className="flex justify-center">
<IconCardGroup items={data} disabled={false} onChange={() => {}} />
</div>
</div>
</Dialog>
);
};

View File

@ -0,0 +1,3 @@
import { Root } from './HasuraFamiliaritySurvey';
export const HasuraFamiliaritySurvey = Root;

View File

@ -0,0 +1,96 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import { ComponentMeta, Story } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { FaStar, FaHeart, FaUser, FaBookmark } from 'react-icons/fa';
import { waitFor } from '@testing-library/react';
import { expect } from '@storybook/jest';
import { IconCardGroup } from './IconCardGroup';
export default {
title: 'components/IconCardGroup',
component: IconCardGroup,
argTypes: {
onCardClick: { action: true },
},
} as ComponentMeta<typeof IconCardGroup>;
type Value = '1' | '2' | '3' | '4';
const data: {
value: Value;
icon: React.ReactNode;
title: string;
body: string;
}[] = [
{
value: '1',
icon: <FaStar className="text-yellow-500 text-xl" />,
title: 'Card-1',
body: 'Description of card-1',
},
{
value: '2',
icon: <FaHeart className="text-yellow-500 text-xl" />,
title: 'Card-2',
body: 'Description of card-2',
},
{
value: '3',
icon: <FaUser className="text-yellow-500 text-xl" />,
title: 'Card-3',
body: 'Description of card-3',
},
{
value: '4',
icon: <FaBookmark className="text-yellow-500 text-xl" />,
title: 'Card-4',
body: 'Description of card-4',
},
];
export const CardsWithValue: Story = () => {
return (
<IconCardGroup<Value> onChange={action('select')} items={data} value="1" />
);
};
export const CardsWithoutValue: Story = () => {
return <IconCardGroup<Value> onChange={action('select')} items={data} />;
};
export const Playground: Story = () => {
const [selected, setSelected] = React.useState<string | undefined>(undefined);
return (
<IconCardGroup
onChange={value => setSelected(value)}
items={data}
value={selected}
/>
);
};
export const PlaygroundWithTest: Story = args => {
return (
<IconCardGroup onChange={value => args.onCardClick(value)} items={data} />
);
};
PlaygroundWithTest.play = async ({ args, canvasElement }) => {
const canvas = within(canvasElement);
// Click on the first card, and expect the onChange prop to be called with `1`
await userEvent.click(canvas.getByText('Card-1'));
await waitFor(() => expect(args.onCardClick).toHaveBeenCalledTimes(1));
expect(args.onCardClick).toBeCalledWith('1');
// Click on the fourth card, and expect the onChange prop to be called with `4`
await userEvent.click(canvas.getByText('Card-4'));
await waitFor(() => expect(args.onCardClick).toHaveBeenCalledTimes(2));
expect(args.onCardClick).toBeCalledWith('4');
};

View File

@ -0,0 +1,60 @@
import clsx from 'clsx';
import React from 'react';
import { FaAngleRight } from 'react-icons/fa';
interface IconCardGroupItem<T> {
value: T;
icon: React.ReactNode;
title: string;
body: string | React.ReactNode;
}
interface IconCardGroupProps<T> {
items: Array<IconCardGroupItem<T>>;
onChange: (option: T) => void;
disabled?: boolean;
value?: T;
}
export const IconCardGroup = <T extends string = string>(
props: IconCardGroupProps<T>
) => {
const { value, items, disabled = false, onChange } = props;
return (
<div className="grid gap-sm grid-rows-auto w-full">
{items.map(item => {
const { value: iValue, title, body } = item;
return (
<div
className={clsx(
'bg-white shadow-sm rounded p-md border border-gray-300 flex',
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
value === iValue && 'border-yellow-400'
)}
key={iValue}
onClick={() => !disabled && onChange(iValue)}
>
<div className="mt-2">{item.icon}</div>
<div className="w-9/12 ml-md">
<label
htmlFor={`card-select-${iValue}`}
className={clsx(
'mb-sm font-semibold mt-0.5',
disabled ? 'cursor-not-allowed' : 'cursor-pointer'
)}
>
{title}
</label>
<p className="text-muted">{body}</p>
</div>
<div className="mt-2 ml-auto">
<FaAngleRight className="text-gray-500" />
</div>
</div>
);
})}
<br />
</div>
);
};

View File

@ -0,0 +1 @@
export { IconCardGroup } from './IconCardGroup';