Initial commit
This commit is contained in:
commit
7878e653a0
253 changed files with 24552 additions and 0 deletions
35
src/components/General/List/List.stories.tsx
Normal file
35
src/components/General/List/List.stories.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import { Story, Meta } from '@storybook/react/types-6-0';
|
||||
import List, { ListProps } from './List';
|
||||
import ContextMenuDecorator from '../../../../.storybook/decorators/ContextMenuDecorator';
|
||||
import DropdownMenuDecorator from '../../../../.storybook/decorators/DropdownMenuDecorator';
|
||||
import * as ListItem from './ListItem.stories';
|
||||
|
||||
export default {
|
||||
title: 'List / List',
|
||||
component: List,
|
||||
decorators: [
|
||||
DropdownMenuDecorator,
|
||||
ContextMenuDecorator,
|
||||
],
|
||||
} as Meta;
|
||||
|
||||
const Template: Story<ListProps> = (args) => (
|
||||
<List>
|
||||
{args.children}
|
||||
</List>
|
||||
);
|
||||
|
||||
export const Primary = Template.bind({ });
|
||||
|
||||
Primary.args = {
|
||||
children: (
|
||||
<>
|
||||
<ListItem.Primary {...ListItem.Primary.args as any} />
|
||||
<ListItem.WithLeftClickActions {...ListItem.WithLeftClickActions.args as any} />
|
||||
<ListItem.WithRightClickActions {...ListItem.WithRightClickActions.args as any} />
|
||||
<ListItem.WithSingleLeftClickAction {...ListItem.WithSingleLeftClickAction.args as any} />
|
||||
<ListItem.WithSubtitle {...ListItem.WithSubtitle.args as any} />
|
||||
</>
|
||||
),
|
||||
};
|
15
src/components/General/List/List.tsx
Normal file
15
src/components/General/List/List.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
|
||||
export interface ListProps {
|
||||
children: ReactNode,
|
||||
}
|
||||
|
||||
export default function List({
|
||||
children,
|
||||
}: ListProps) {
|
||||
return (
|
||||
<ul>
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
}
|
59
src/components/General/List/ListItem.stories.tsx
Normal file
59
src/components/General/List/ListItem.stories.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
import React from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { Story, Meta } from '@storybook/react/types-6-0';
|
||||
import ListItem, { ListItemProps } from './ListItem';
|
||||
import ContextMenuDecorator from '../../../../.storybook/decorators/ContextMenuDecorator';
|
||||
import DropdownMenuDecorator from '../../../../.storybook/decorators/DropdownMenuDecorator';
|
||||
|
||||
export default {
|
||||
title: 'List / List Item',
|
||||
component: ListItem,
|
||||
decorators: [
|
||||
DropdownMenuDecorator,
|
||||
ContextMenuDecorator,
|
||||
],
|
||||
} as Meta;
|
||||
|
||||
const Template: Story<ListItemProps> = (args) => (
|
||||
<ListItem {...args} />
|
||||
);
|
||||
|
||||
export const Primary = Template.bind({ });
|
||||
|
||||
Primary.args = {
|
||||
title: 'List item title',
|
||||
};
|
||||
|
||||
export const WithSubtitle = Template.bind({ });
|
||||
|
||||
WithSubtitle.args = {
|
||||
...Primary.args,
|
||||
subtitle: 'List item subtitle',
|
||||
};
|
||||
|
||||
export const WithSingleLeftClickAction = Template.bind({ });
|
||||
|
||||
WithSingleLeftClickAction.args = {
|
||||
...Primary.args,
|
||||
onClick: action('Single left click'),
|
||||
};
|
||||
|
||||
export const WithLeftClickActions = Template.bind({ });
|
||||
|
||||
WithLeftClickActions.args = {
|
||||
...Primary.args,
|
||||
primaryClickActions: [
|
||||
{ label: 'Action 1', onClick: action('Action 1 click') },
|
||||
{ label: 'Action 2', onClick: action('Action 2 click') },
|
||||
],
|
||||
};
|
||||
|
||||
export const WithRightClickActions = Template.bind({ });
|
||||
|
||||
WithRightClickActions.args = {
|
||||
...Primary.args,
|
||||
secondaryClickActions: [
|
||||
{ label: 'Action 1', onClick: action('Action 1 click') },
|
||||
{ label: 'Action 2', onClick: action('Action 2 click') },
|
||||
],
|
||||
};
|
15
src/components/General/List/ListItem.test.tsx
Normal file
15
src/components/General/List/ListItem.test.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
import { render, testId } from '../../../tests/enzyme';
|
||||
import ListItem from './ListItem';
|
||||
|
||||
describe('ListItem', () => {
|
||||
it('displays the title', () => {
|
||||
const wrapper = render(<ListItem title="Test Title" />);
|
||||
expect(wrapper.find(testId('title')).text()).toEqual('Test Title');
|
||||
});
|
||||
|
||||
it('displays the subtitle', () => {
|
||||
const wrapper = render(<ListItem title="Test Title" subtitle="Subtitle" />);
|
||||
expect(wrapper.find(testId('subtitle')).text()).toEqual('Subtitle');
|
||||
});
|
||||
});
|
120
src/components/General/List/ListItem.tsx
Normal file
120
src/components/General/List/ListItem.tsx
Normal file
|
@ -0,0 +1,120 @@
|
|||
import React, {
|
||||
ReactElement,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { FaEllipsisH } from 'react-icons/fa';
|
||||
import tw from 'twin.macro';
|
||||
import { ContextMenuContext } from '../../../providers/ContextMenuProvider';
|
||||
import { DropdownMenuContext } from '../../../providers/DropdownMenuProvider';
|
||||
import ContextMenuAction from '../../../providers/context-menu-action';
|
||||
|
||||
export interface ListItemProps {
|
||||
title: ReactElement | string,
|
||||
subtitle?: ReactElement | string,
|
||||
isSelected?: boolean,
|
||||
onClick?: (event: React.MouseEvent) => void,
|
||||
primaryClickActions?: ContextMenuAction[],
|
||||
secondaryClickActions?: ContextMenuAction[],
|
||||
}
|
||||
|
||||
export default function ListItem({
|
||||
title,
|
||||
subtitle,
|
||||
isSelected: isSelectedExternal,
|
||||
onClick,
|
||||
primaryClickActions,
|
||||
secondaryClickActions,
|
||||
}: ListItemProps) {
|
||||
const contextMenu = useContext(ContextMenuContext);
|
||||
const dropdownMenu = useContext(DropdownMenuContext);
|
||||
|
||||
const [isSelected, setIsSelected] = useState<boolean>(false);
|
||||
const [isDropdownSelected, setIsDropdownSelected] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<li
|
||||
className="group"
|
||||
css={[
|
||||
tw`flex border-b last:border-b-0 border-gray-200 dark:border-gray-700 cursor-pointer hover:bg-gray-100 hover:dark:bg-gray-800`,
|
||||
(isSelected || isSelectedExternal) && tw`bg-gray-100 dark:bg-gray-800`,
|
||||
]}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={async (event) => {
|
||||
if (primaryClickActions?.length) {
|
||||
setIsSelected(true);
|
||||
await contextMenu.openForMouseEvent(
|
||||
event,
|
||||
primaryClickActions,
|
||||
);
|
||||
setIsSelected(false);
|
||||
}
|
||||
|
||||
onClick?.(event);
|
||||
}}
|
||||
onContextMenu={async (event) => {
|
||||
if (secondaryClickActions?.length) {
|
||||
setIsSelected(true);
|
||||
await contextMenu.openForMouseEvent(
|
||||
event,
|
||||
secondaryClickActions,
|
||||
);
|
||||
setIsSelected(false);
|
||||
}
|
||||
}}
|
||||
tw="w-full flex flex-row items-center py-2 px-4"
|
||||
>
|
||||
<div tw="flex flex-col flex-grow text-left">
|
||||
<div
|
||||
tw="text-gray-800 dark:text-gray-200"
|
||||
data-testid="title"
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
{subtitle && (
|
||||
<div
|
||||
tw="pt-2 text-gray-500 dark:text-gray-400 text-xs"
|
||||
data-testid="subtitle"
|
||||
>
|
||||
{subtitle}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!!secondaryClickActions?.length && (
|
||||
<div
|
||||
role="presentation"
|
||||
css={[
|
||||
tw`ml-2 text-xs text-gray-700 dark:text-gray-300 p-1 ml-2 hover:bg-gray-300 hover:dark:bg-gray-700`,
|
||||
!isDropdownSelected && tw`invisible group-hover:visible`,
|
||||
isDropdownSelected && tw`bg-gray-300 dark:bg-gray-900 visible`,
|
||||
]}
|
||||
onClick={async (event) => {
|
||||
event.stopPropagation();
|
||||
setIsSelected(true);
|
||||
setIsDropdownSelected(true);
|
||||
await dropdownMenu.openForElement(
|
||||
event.currentTarget as HTMLElement,
|
||||
secondaryClickActions!,
|
||||
);
|
||||
setIsSelected(false);
|
||||
setIsDropdownSelected(false);
|
||||
}}
|
||||
onContextMenu={(event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<FaEllipsisH />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
ListItem.defaultProps = {
|
||||
primaryClickActions: [],
|
||||
secondaryClickActions: [],
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue