Skip to content

Commit

Permalink
Merge pull request #165 from Espressive/feature/fds-142
Browse files Browse the repository at this point in the history
feat(Table): Conditional actions
  • Loading branch information
brianespinosa authored Mar 3, 2021
2 parents 8c42b88 + b4e0264 commit 7a761ac
Show file tree
Hide file tree
Showing 6 changed files with 352 additions and 63 deletions.
1 change: 1 addition & 0 deletions packages/cascara/src/lib/mock/fakeData.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const generateFakeInteractions = (qty) =>
matchedIntent,
phrase: faker.lorem.sentence(5),
response: faker.random.boolean(),
type: faker.random.arrayElement(['faq', 'other', 'interaction']),
user: `${firstName} ${lastName}`,
});
});
4 changes: 3 additions & 1 deletion packages/cascara/src/ui/ActionsMenu/ActionsMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ const ActionsMenu = ({ trigger = DEFAULT_TRIGGER, actions }) => {
const handleMenuItemClick = (item) => {
menu.hide();

onAction(item, record);
if (onAction) {
onAction(item, record);
}
};

return (
Expand Down
106 changes: 58 additions & 48 deletions packages/cascara/src/ui/Table/0-Table.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,35 +38,47 @@ const data = {[
]}
```

## dataConfig

Here you can specify the columns to display as well as the available actions (if any) for each row.
This is the core of Table's configuration, here you can define:

### actions
## actions

Actions represent 'verbs' we want to apply to our Table rows, like 'view' to navigate to another page with detailed information about the row.
Another example would be 'Delete' to delete a row. 'actions' will be appended to each row, they'll appear as buttons.
By defining an 'action', you are telling Table how you want to be notified when your 'action' button is pressed. Via the 'onAction' event.

> Table does not provide the logic to handle 'actions', the implementation for deleting a row, navigating to another page, etc. it's up to you.
By defining an 'action', you are telling Table how you want to be notified when your 'action' button is pressed. Via the 'onAction' event.
The shape of `actions` is as follows:

```javascript
---
title: The shape of an action
---
const action = {
module: 'button', // either button or edit
name: 'view', // the action name
content: 'View', // button text
size: 'small', // button size
},
```javasccript
const actions = {
actionButtonMenuIndex: 0,
modules: [
{
module: 'button',
name: 'view',
content: 'view',
},
{
module: 'edit',
cancelLabel: 'Cancelar',
editLabel: 'Editar',
saveLabel: 'Guardar',
},
],
resolveRecordActions: (record, actions) => (actionsForRecord),
}
```

#### Action modules
### actionButtonMenuIndex

Tables can become bloated if you use lots of actions, in order to prevent this, the actions are wrapped by an `ActionsMenu`, a contextual menu accessible via the `meatball` button of each row.

At the moment, we have only two types of modules.
You can specify which actions are sent to the `ActionsMenu` using the `actionButtonMenuIndex` prop, a number that acts like a partition index that will take your actions array and slice it. The actions whose index is greater or equal to `actionButtonMenuIndex` will be part of `ActionsMenu`, the rest will be displayed as usual.

> This only applies to `button` actions, `edit` actions will always be displayed as usual.
### modules

At the moment, we have only two types of action modules.

- `button`, for simple actions like deleting a row, navigating to another page, etc.
- `edit`, to be used if you want to allow the data to be `editable`.
Expand All @@ -80,43 +92,41 @@ In the case of `edit`, the `onAction` event will be emitted up to 3 times as it

All this information is part of the `onAction` event signature, please make sure you review it as well.

An example set of actions:
### resolveRecordActions

```javascript
---
title: An example set of actions
---
A function that returns the actions available to the current row.

const actions: [
```javasccript
resolveRecordActions(record, actions) {
return actions.reduce((actionsForRecord, action) => {
switch (action.name) {
case 'edit':
// do not show if record is deflected
if (!record.deflected) {
actionsForRecord.push(action);
}
break;
// simple button
{
module: 'button',
name: 'view',
content: 'view',
size: 'small',
},
case 'view.faq':
// do not show view button for FAQs
if (record.type !== 'faq') {
actionsForRecord.push(action);
}
break;
// or
default:
actionsForRecord.push(action);
}
// edit button combo (for editable rows)
{
module: 'edit',
size: 'small',
cancelLabel: 'Cancelar', // you can specify localized labels
editLabel: 'Editar',
saveLabel: 'Guardar',
},
];
return actionsForRecord;
}, []);
};
```

### actionButtonMenuIndex

Tables can become bloated if you use lots of actions, in order to prevent this, the actions are wrapped by an `ActionsMenu`, a contextual menu accessible via the `meatball` button of each row.

You can specify which actions are sent to the `ActionsMenu` using the `actionButtonMenuIndex` prop, a number that acts like a partition index that will take your actions array and slice it. The actions whose index is greater or equal to `actionButtonMenuIndex` will be part of `ActionsMenu`, the rest will be displayed as usual.
## dataConfig

> This only applies to `button` actions, `edit` actions will always be displayed as usual.
Here you can specify the columns to display as well as the available actions (if any) for each row.
This is the core of Table's configuration, here you can define:

### display

Expand Down
59 changes: 54 additions & 5 deletions packages/cascara/src/ui/Table/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ const actionModuleOptions = Object.keys(actionModules);
const dataModuleOptions = Object.keys(dataModules);

const propTypes = {
/** Actions will be appended to each row, they'll appear as buttons. */
actions: pt.shape({
actionButtonMenuIndex: pt.number,

modules: pt.arrayOf(
pt.shape({
module: pt.oneOf(actionModuleOptions).isRequired,
})
),

/** Resolve record actions.
* A function that returns the actions available to the current row */
resolveRecordActions: pt.func,
}),

/** An array of objects.
*
* Every object in this array will potencially be rendered as a table row. */
Expand All @@ -23,6 +38,7 @@ const propTypes = {
* as well as the available actions (if any) for each row. */
dataConfig: pt.shape({
actionButtonMenuIndex: pt.number,

/** Actions will be appended to each row, they'll appear as buttons. */
actions: pt.arrayOf(
pt.shape({
Expand All @@ -43,6 +59,10 @@ const propTypes = {
* An event handler you can pass to handle every event your table emits.*/
onAction: pt.func,

/** Resolve record actions.
* A function that returns the actions available to the current row */
resolveRecordActions: pt.func,

/** Unique ID Attribute.
*
* specifies the attribute that uniquely identifies every object in the 'data' array. */
Expand All @@ -51,26 +71,55 @@ const propTypes = {

/** This is a Table */
const Table = ({
data = [],
dataConfig = {},
onAction = (type, data) => type,
actions,
data,
dataConfig,
onAction,
uniqueIdAttribute,
...rest
}) => {
const { actions = [], display = [] } = dataConfig;
const display = dataConfig?.display;

// // FDS-142: new action props
let actionButtonMenuIndex = actions?.actionButtonMenuIndex;
let modules = actions?.modules;
let resolveRecordActions = actions?.resolveRecordActions;

// old action props
const unwantedActions = dataConfig?.actions;
if (unwantedActions) {
modules = unwantedActions;
// eslint-disable-next-line no-console
console.warn(
'Prop "dataConfig.actions" has been deprecated. Actions have been moved to the root of the Table component as their own prop.'
);
}

const unwantedActionButtonIndex = dataConfig?.actionButtonIndex;
if (unwantedActionButtonIndex) {
actionButtonMenuIndex = unwantedActionButtonIndex;
// eslint-disable-next-line no-console
console.warn(
'Prop "dataConfig.actionButtonIndex" has been deprecated. Actions have been moved to the root of the Table component as their own prop.'
);
}

let columnCount = display.length;

if (actions.length) {
if (modules.length) {
columnCount++;
}

return (
<ErrorBoundary>
<TableProvider
value={{
actionButtonMenuIndex,
data,
dataConfig,
modules,
onAction,
resolveRecordActions,
uniqueIdAttribute,
}}
{...rest}
Expand Down
23 changes: 14 additions & 9 deletions packages/cascara/src/ui/Table/TableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,26 @@ const propTypes = {
const TableRow = ({ config = {}, record = {} }) => {
const { id, columns } = config;
const {
dataConfig: { actionButtonMenuIndex = 0, actions: userDefinedActions = [] },
resolveRecordActions,
actionButtonMenuIndex = 0,
modules: userDefinedModules = [],
} = useContext(ModuleContext);

// FDS-142: If a resolver is passed, get actions from it
const actions = resolveRecordActions
? resolveRecordActions(record, userDefinedModules)
: userDefinedModules; // otherwise continue as normal

const outsideButtonActions = [];
const insideButtonActions = [];
userDefinedActions
actions
.filter(({ module }) => module === 'button')
.map((action, index) =>
index >= actionButtonMenuIndex
? insideButtonActions.push(action)
: outsideButtonActions.push(action)
);
const specialActions = userDefinedActions.filter(
({ module }) => module !== 'button'
);

const specialActions = actions.filter(({ module }) => module !== 'button');
const outsideActions = [...specialActions, ...outsideButtonActions];

const renderActionModule = (action, index) => {
Expand All @@ -68,7 +73,7 @@ const TableRow = ({ config = {}, record = {} }) => {
);
};

const actions = (
const rowActions = (
<td className={styles.CellActions} key={`${id}-actionbar`}>
{outsideActions.map(renderActionModule)}
{Boolean(insideButtonActions.length) ? (
Expand All @@ -93,8 +98,8 @@ const TableRow = ({ config = {}, record = {} }) => {
);
});

if (userDefinedActions.length) {
rowCells.push(actions);
if (userDefinedModules.length) {
rowCells.push(rowActions);
}

return (
Expand Down
Loading

0 comments on commit 7a761ac

Please sign in to comment.