Skip to content

Commit

Permalink
Feat/document list (#80)
Browse files Browse the repository at this point in the history
* feat: added `ActionBar`, `SelectableList` and `DocumentList`

refactor: moved action bar from `ThumbnailGrid` into `ActionBar`

refactor: updated `SearchPage`
  • Loading branch information
QuentinLeCaignec authored Dec 8, 2023
1 parent a0aa52b commit 0e819ee
Show file tree
Hide file tree
Showing 23 changed files with 1,887 additions and 182 deletions.
8 changes: 8 additions & 0 deletions .changeset/brown-foxes-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@smile/react-front-kit': minor
'storybook-pages': minor
---

Extracted action bar from `ThumbnailGrid` into new component.
Added `ActionBar`, `SelectableList` and `DocumentList` components.
Updated `SearchPage` example page.
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { IActionBarProps } from './ActionBar';
import type { IThumbnail, IThumbnailAction } from '../../types';

import { Trash } from '@phosphor-icons/react';
import { FolderMove } from '@smile/react-front-kit-shared';
import { action } from '@storybook/addon-actions';

export const actionBarActionsMock: IThumbnailAction[] = [
{
icon: <FolderMove size={16} />,
id: 'move',
isItemAction: false,
isMassAction: true,
label: 'Move in tree',
onAction: action('Move selected in tree'),
},
{
color: 'red',
confirmModalProps: {
cancelLabel: 'Abort',
children: <p>Are you sure ?</p>,
confirmColor: 'red',
confirmLabel: 'Remove',
title: 'remove files ?',
},
confirmation: true,
icon: <Trash size={16} />,
id: 'delete',
isItemAction: false,
isMassAction: true,
label: 'Delete',
onAction: action('Delete selected'),
},
];

export const actionBarSelectedMock: IThumbnail[] = [
{
id: '1',
label: 'Label A',
},
{
id: '2',
label: 'Label B',
},
{
id: '3',
label: 'Label C',
},
];

export const actionBarLabelMock = (elements: number): string =>
`${elements} selected`;

export const actionBarMock: IActionBarProps<IThumbnail> = {
actions: actionBarActionsMock,
selectedElements: actionBarSelectedMock,
selectedElementsLabel: actionBarLabelMock,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { IActionBarAction } from './ActionBar';
import type { Meta, StoryObj } from '@storybook/react';

import { ActionBar as Cmp } from './ActionBar';
import {
actionBarActionsMock,
actionBarLabelMock,
actionBarSelectedMock,
} from './ActionBar.mock';

const meta = {
component: Cmp,
tags: ['autodocs'],
title: '3-custom/Components/ActionBar',
} satisfies Meta<typeof Cmp>;

export default meta;
type IStory = StoryObj<typeof meta>;

export const ActionBar: IStory = {
args: {
actions: actionBarActionsMock as IActionBarAction<
Record<string, unknown>
>[],
selectedElements: actionBarSelectedMock,
selectedElementsLabel: actionBarLabelMock,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { renderWithProviders } from '@smile/react-front-kit-shared/test-utils';

import { ActionBar } from './ActionBar';
import { actionBarMock } from './ActionBar.mock';

describe('ActionBar', () => {
it('matches snapshot', () => {
const { container } = renderWithProviders(<ActionBar {...actionBarMock} />);
expect(container).toMatchSnapshot();
});
});
129 changes: 129 additions & 0 deletions packages/react-front-kit/src/Components/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import type { GroupProps, ModalProps } from '@mantine/core';
import type { Record } from '@phosphor-icons/react';
import type {
IAction,
IActionConfirmModalProps,
} from '@smile/react-front-kit-shared';
import type { ReactElement } from 'react';

import { Button, Group } from '@mantine/core';
import { createStyles } from '@mantine/styles';
import { useState } from 'react';

import { ConfirmModal } from '../ConfirmModal/ConfirmModal';

const useStyles = createStyles((theme) => ({
actionBar: {
alignItems: 'center',
background: theme.fn.primaryColor(),
borderRadius: 4,
color: 'white',
display: 'inline-flex',
justifyContent: 'space-between',
padding: '16px 24px',
width: '100%',
},
}));

export type IActionBarAction<Data extends Record<string, unknown>> = IAction<
Data | Data[]
>;

export interface IActionBarProps<Data extends Record<string, unknown>>
extends GroupProps {
actions?: IActionBarAction<Data>[];
modalProps?: Omit<ModalProps, 'title'>;
selectedElements: Data[];
selectedElementsLabel?: (selectedElements: number) => string;
}

export function ActionBar<Data extends Record<string, unknown>>(
props: IActionBarProps<Data>,
): ReactElement {
const {
actions,
modalProps,
selectedElements,
selectedElementsLabel = (selectedElements: number) =>
`${selectedElements} file(s) selected`,
...groupProps
} = props;
const [confirmAction, setConfirmAction] = useState<IActionConfirmModalProps<
Data | Data[]
> | null>(null);
const numberOfSelectedElements = selectedElements.length;

const { classes } = useStyles();

function setModal(action: IAction<Data>): void {
setConfirmAction({
cancelColor: action.confirmModalProps?.cancelColor,
cancelLabel: action.confirmModalProps?.cancelLabel,
children: action.confirmModalProps?.children,
confirmColor: action.confirmModalProps?.confirmColor,
confirmLabel: action.confirmModalProps?.confirmLabel,
onConfirm: () => action.onAction?.(selectedElements),
title: action.confirmModalProps?.title,
});
}

function clearConfirmAction(): void {
setConfirmAction(null);
}

function handleClose(): void {
clearConfirmAction();
}

function handleModalButton(onAction?: (item: Data[]) => void): void {
onAction?.(selectedElements);
handleClose();
}

function handleGridAction(action: IAction<Data>): void {
if (action.confirmation) {
setModal(action);
} else {
action.onAction?.(selectedElements);
}
}

return (
<>
<div className={classes.actionBar}>
<span>{selectedElementsLabel(numberOfSelectedElements)}</span>
{actions && actions.length > 0 ? (
<Group {...groupProps}>
{actions.map((action) => (
<Button
key={action.id}
color={action.color}
leftIcon={
typeof action.icon === 'function'
? action.icon(selectedElements)
: action.icon
}
onClick={() => handleGridAction(action)}
variant={action.color ? 'filled' : 'default'}
>
{typeof action.label === 'function'
? action.label(selectedElements)
: action.label}
</Button>
))}
</Group>
) : null}
</div>
<ConfirmModal
{...confirmAction}
onCancel={() => handleModalButton(confirmAction?.onCancel)}
onClose={handleClose}
onConfirm={() => handleModalButton(confirmAction?.onConfirm)}
opened={Boolean(confirmAction)}
{...modalProps}
>
{confirmAction?.children}
</ConfirmModal>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ActionBar matches snapshot 1`] = `
<div>
<div
class="mantine-zzdfuz"
>
<span>
3 selected
</span>
<div
class="mantine-Group-root mantine-k3ov3c"
>
<button
class="mantine-UnstyledButton-root mantine-Button-root mantine-1q6w75n"
data-button="true"
type="button"
>
<div
class="mantine-1wpc1xj mantine-Button-inner"
>
<span
class="mantine-Button-icon mantine-Button-leftIcon mantine-1jbrfp"
>
<svg
fill="none"
height="16"
stroke="currentColor"
viewBox="0 0 12 12"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.58333 10H1.41667C1.30616 10 1.20018 9.95564 1.12204 9.87668C1.0439 9.79771 1 9.69062 1 9.57895V2.42105C1 2.30938 1.0439 2.20229 1.12204 2.12332C1.20018 2.04436 1.30616 2 1.41667 2H4.19271C4.2828 2.00037 4.37041 2.02988 4.44271 2.08421L5.89062 3.17895C5.96292 3.23327 6.05053 3.26278 6.14062 3.26316H10.5833C10.6938 3.26316 10.7998 3.30752 10.878 3.38648C10.9561 3.46544 11 3.57254 11 3.68421V5.36842"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M6 9H11"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M9 7L11 9L9 11"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
<span
class="mantine-1ryt1ht mantine-Button-label"
>
Move in tree
</span>
</div>
</button>
<button
class="mantine-UnstyledButton-root mantine-Button-root mantine-3agwv"
data-button="true"
type="button"
>
<div
class="mantine-1wpc1xj mantine-Button-inner"
>
<span
class="mantine-Button-icon mantine-Button-leftIcon mantine-1jbrfp"
>
<svg
fill="currentColor"
height="16"
viewBox="0 0 256 256"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"
/>
</svg>
</span>
<span
class="mantine-1ryt1ht mantine-Button-label"
>
Delete
</span>
</div>
</button>
</div>
</div>
</div>
`;
Loading

0 comments on commit 0e819ee

Please sign in to comment.