Skip to content

Commit

Permalink
Merge branch '0.9.x' into feat/ButtonsListOrMenuDropDown
Browse files Browse the repository at this point in the history
  • Loading branch information
vapersmile authored Jan 22, 2024
2 parents b4b5667 + a54b91d commit 6cdfacf
Show file tree
Hide file tree
Showing 17 changed files with 852 additions and 303 deletions.
6 changes: 6 additions & 0 deletions .changeset/strange-cooks-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@smile/react-front-kit': minor
'storybook-pages': minor
---

Extracted logic from ActionBar into new ActionRowOverflow, moved a type and some mocks
Original file line number Diff line number Diff line change
@@ -1,58 +1,16 @@
import type { IActionBarProps } from './ActionBar';
import type { IThumbnail, IThumbnailAction } from '../../types';
import type { IThumbnail } 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',
},
];
import {
actionRowOverflowActionsMock,
actionRowOverflowSelectedMock,
} from '../ActionRowOverflow/ActionRowOverflow.mock';

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

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

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

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

const meta = {
component: Cmp,
Expand All @@ -19,11 +20,11 @@ type IStory = StoryObj<typeof meta>;

export const ActionBar: IStory = {
args: {
actions: actionBarActionsMock as IActionBarAction<
actions: actionRowOverflowActionsMock as IActionRowOverflowAction<
Record<string, unknown>
>[],
rowActionNumber: 1,
selectedElements: actionBarSelectedMock,
rowActionNumber: 2,
selectedElements: actionRowOverflowSelectedMock,
selectedElementsLabel: actionBarLabelMock,
},
};
194 changes: 11 additions & 183 deletions packages/react-front-kit/src/Components/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
'use client';

import type { GroupProps, ModalProps } from '@mantine/core';
import type { IActionRowOverflowProps } from '../ActionRowOverflow/ActionRowOverflow';
import type { Record } from '@phosphor-icons/react';
import type {
IAction,
IActionConfirmModalProps,
} from '@smile/react-front-kit-shared';
import type { ReactElement } from 'react';

import { ActionIcon, Button, Group, Menu } from '@mantine/core';
import { createStyles, getStylesRef } from '@mantine/styles';
import { DotsThreeVertical } from '@phosphor-icons/react';
import { useState } from 'react';

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

const useStyles = createStyles((theme) => ({
actionBar: {
Expand All @@ -27,197 +20,32 @@ const useStyles = createStyles((theme) => ({
ref: getStylesRef('actionBar'),
width: '100%',
},
actionIconRoot: {
borderRadius: '32px',
height: '32px',
width: '32px',
},
buttonIcon: {
[theme.fn.smallerThan('md')]: {
marginRight: 0,
},
},
buttonLabel: {
[theme.fn.smallerThan('md')]: {
display: 'none',
},
},
buttonRoot: {
[theme.fn.smallerThan('md')]: {
height: '32px',
padding: '0',
width: '32px',
},
},
groupRoot: {
gap: '8px',
},
menuDropdown: {
borderRadius: '4px',
minWidth: '200px',
},
}));

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

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

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

const { classes } = useStyles();

function setModal(action: IActionBarAction<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: IActionBarAction<Data>): void {
if (action.confirmation) {
setModal(action);
} else {
action.onAction?.(selectedElements);
}
}

function handleMenuItem(action: IActionBarAction<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 className={classes.groupRoot} {...groupProps}>
{visibleRowActions?.map((action) => (
<Button
key={action.id}
classNames={{
icon: classes.buttonIcon,
label: classes.buttonLabel,
root: classes.buttonRoot,
}}
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>
))}
{menuRowActions?.length !== undefined &&
menuRowActions.length > 0 ? (
<div>
<Menu
classNames={{ dropdown: classes.menuDropdown }}
shadow="lg"
>
<Menu.Target>
<ActionIcon
className={classes.actionIconRoot}
onClick={(e) => e.stopPropagation()}
type="button"
variant="light"
>
<div>
<DotsThreeVertical size={16} weight="bold" />
</div>
</ActionIcon>
</Menu.Target>
<Menu.Dropdown onClick={(e) => e.stopPropagation()}>
{menuRowActions.map((action, index) => (
<Menu.Item
// eslint-disable-next-line react/no-array-index-key
key={index}
color={action.color}
icon={
typeof action.icon === 'function'
? action.icon(selectedElements)
: action.icon
}
onClick={() => handleMenuItem(action)}
{...(typeof action.componentProps === 'function'
? action.componentProps(selectedElements)
: action.componentProps)}
>
{typeof action.label === 'function'
? action.label(selectedElements)
: action.label}
</Menu.Item>
))}
</Menu.Dropdown>
</Menu>
</div>
) : null}
</Group>
) : null}
</div>
<ConfirmModal
{...confirmAction}
onCancel={() => handleModalButton(confirmAction?.onCancel)}
onClose={handleClose}
onConfirm={() => handleModalButton(confirmAction?.onConfirm)}
opened={Boolean(confirmAction)}
{...modalProps}
>
{confirmAction?.children}
</ConfirmModal>
</>
<div className={classes.actionBar}>
<span>{selectedElementsLabel(numberOfSelectedElements)}</span>
<ActionRowOverflow
{...actionRowOverflowProps}
selectedElements={selectedElements}
/>
</div>
);
}
Loading

0 comments on commit 6cdfacf

Please sign in to comment.