Skip to content

Commit

Permalink
feat: wrap bulk actions with ButtonGroup
Browse files Browse the repository at this point in the history
now bulk actions have the same behaviour as other actions:
- they can have variants
- they can be nested under parents
  • Loading branch information
wojtek-krysiak committed Oct 5, 2020
1 parent 28142b3 commit 203268f
Show file tree
Hide file tree
Showing 9 changed files with 40 additions and 52 deletions.
2 changes: 1 addition & 1 deletion .cspell.json
Expand Up @@ -7,7 +7,7 @@
"codecov", "bulma", "unmount", "testid", "woff", "iife", "sourcemap", "Roboto",
"camelize", "datepicker", "camelcase", "fullwidth", "wysiwig", "Helvetica", "Neue",
"Arial", "nowrap", "textfield", "scrollable", "flexbox", "treal", "xxxl",
"adminbro", "Checkmark", "overridable", "Postgres", "Hana", "Wojtek", "Krysiak"
"adminbro", "Checkmark", "overridable", "Postgres", "Hana", "Wojtek", "Krysiak", "bigint"
],
"ignorePaths": [
"src/frontend/assets/**/*"
Expand Down
6 changes: 1 addition & 5 deletions src/frontend/components/app/action-header/action-header.tsx
@@ -1,7 +1,7 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import React from 'react'
import { Box, Badge, H3, H2, ButtonGroup, cssClass } from '@admin-bro/design-system'
import { useHistory, useLocation } from 'react-router'
import { useHistory } from 'react-router'

import Breadcrumbs from '../breadcrumbs'
import { ActionHeaderProps } from './action-header-props'
Expand Down Expand Up @@ -29,7 +29,6 @@ export const ActionHeader: React.FC<ActionHeaderProps> = (props) => {
} = props

const { translateButton } = useTranslation()
const location = useLocation()
const history = useHistory()
const actionResponseHandler = useActionResponseHandler(actionPerformed)

Expand All @@ -45,7 +44,6 @@ export const ActionHeader: React.FC<ActionHeaderProps> = (props) => {
action: sourceAction,
params,
actionResponseHandler,
search: location.search,
push: history.push,
})(event)
)
Expand All @@ -56,7 +54,6 @@ export const ActionHeader: React.FC<ActionHeaderProps> = (props) => {
// only new action should be seen in regular "Big" actions place
: resource.resourceActions.filter(ra => ra.name === 'new' && (!action || action.name !== ra.name)),
params,
search: location.search,
handleClick: handleActionClick,
})

Expand All @@ -72,7 +69,6 @@ export const ActionHeader: React.FC<ActionHeaderProps> = (props) => {
const customResourceButtons = actionsToButtonGroup({
actions: resource.resourceActions.filter(ra => !['list', 'new'].includes(ra.name)),
params: { resourceId },
search: location.search,
handleClick: handleActionClick,
})

Expand Down
Expand Up @@ -16,7 +16,6 @@ describe('actionsToButtonGroup', () => {
}
let buttonGroupProps: ButtonGroupProps['buttons']

const search = ''
const handleClick = () => true

context('flat actions (no nesting)', () => {
Expand All @@ -28,7 +27,6 @@ describe('actionsToButtonGroup', () => {
buttonGroupProps = actionsToButtonGroup({
actions,
params,
search,
handleClick,
})
})
Expand Down Expand Up @@ -75,7 +73,6 @@ describe('actionsToButtonGroup', () => {
...actionsExport,
],
params,
search,
handleClick,
})
})
Expand Down Expand Up @@ -107,7 +104,6 @@ describe('actionsToButtonGroup', () => {
buttonGroupProps = actionsToButtonGroup({
actions,
params,
search,
handleClick,
})
})
Expand Down
Expand Up @@ -6,16 +6,15 @@ import { DifferentActionParams } from '../../../hooks'
export type actionsToButtonGroupOptions = {
actions: Array<ActionJSON>;
params: DifferentActionParams;
search: Location['search'];
handleClick: ButtonInGroupProps['onClick'];
}

export const actionsToButtonGroup = (
options: actionsToButtonGroupOptions,
): ButtonGroupProps['buttons'] => {
const { actions, params, search, handleClick } = options
const { actions, params, handleClick } = options
const buttons = actions.map((action) => {
const href = actionHref(action, params, search)
const href = actionHref(action, params)
return {
icon: action.icon,
label: action.label,
Expand Down
5 changes: 0 additions & 5 deletions src/frontend/components/app/records-table/record-in-list.tsx
Expand Up @@ -4,7 +4,6 @@ import {
Placeholder, TableRow, TableCell, CheckBox, ButtonGroup,
} from '@admin-bro/design-system'

import { useLocation } from 'react-router'
import PropertyType from '../../property-type'
import { ActionJSON, buildActionClickHandler, RecordJSON, ResourceJSON } from '../../../interfaces'
import { display } from './utils/display'
Expand All @@ -29,7 +28,6 @@ export const RecordInList: React.FC<RecordInListProps> = (props) => {
} = props
const [record, setRecord] = useState<RecordJSON>(recordFromProps)
const history = useHistory()
const location = useLocation()

const handleActionCallback = useCallback((actionResponse: ActionResponse) => {
if (actionResponse.record && !actionResponse.redirectUrl) {
Expand Down Expand Up @@ -62,7 +60,6 @@ export const RecordInList: React.FC<RecordInListProps> = (props) => {
action,
params: { resourceId: resource.id, recordId: record.id },
actionResponseHandler,
search: location.search,
push: history.push,
})(event)
}
Expand All @@ -75,7 +72,6 @@ export const RecordInList: React.FC<RecordInListProps> = (props) => {
action: sourceAction,
params: actionParams,
actionResponseHandler,
search: location.search,
push: history.push,
})(event)
)
Expand All @@ -88,7 +84,6 @@ export const RecordInList: React.FC<RecordInListProps> = (props) => {
buttons: actionsToButtonGroup({
actions: recordActions,
params: actionParams,
search: location.search,
handleClick: handleActionClick,
}),
}]
Expand Down
50 changes: 29 additions & 21 deletions src/frontend/components/app/records-table/selected-records.tsx
@@ -1,10 +1,11 @@
import React from 'react'
import { TableCaption, Button, Icon, CardTitle } from '@admin-bro/design-system'
import { TableCaption, Title, ButtonGroup, Box } from '@admin-bro/design-system'

import { RecordJSON, ResourceJSON } from '../../../interfaces'
import ActionButton from '../action-button/action-button'
import { useHistory } from 'react-router'
import { ActionJSON, buildActionClickHandler, RecordJSON, ResourceJSON } from '../../../interfaces'
import getBulkActionsFromRecords from './utils/get-bulk-actions-from-records'
import { useTranslation } from '../../../hooks'
import { useActionResponseHandler, useTranslation } from '../../../hooks'
import { actionsToButtonGroup } from '../action-header/actions-to-button-group'

type SelectedRecordsProps = {
resource: ResourceJSON;
Expand All @@ -14,31 +15,38 @@ type SelectedRecordsProps = {
export const SelectedRecords: React.FC<SelectedRecordsProps> = (props) => {
const { resource, selectedRecords } = props
const { translateLabel } = useTranslation()
const history = useHistory()
const actionResponseHandler = useActionResponseHandler()

if (!selectedRecords || !selectedRecords.length) {
return null
}

const bulkActions = getBulkActionsFromRecords(selectedRecords)
const params = { resourceId: resource.id, recordIds: selectedRecords.map(records => records.id) }

const handleActionClick = (event, sourceAction: ActionJSON): void => (
buildActionClickHandler({
action: sourceAction,
params,
actionResponseHandler,
push: history.push,
})(event)
)

const bulkButtons = actionsToButtonGroup({
actions: getBulkActionsFromRecords(selectedRecords),
params,
handleClick: handleActionClick,
})

return (
<TableCaption>
<CardTitle as="span" mr="lg">
{translateLabel('selectedRecords', resource.id, { selected: selectedRecords.length })}
</CardTitle>
{bulkActions.map(action => (
<ActionButton
action={action}
key={action.name}
resourceId={resource.id}
recordIds={selectedRecords.map(records => records.id)}
>
<Button variant="text">
<Icon icon={action.icon} />
{action.label}
</Button>
</ActionButton>
))}
<Box flex py="sm" alignItems="center">
<Title mr="lg">
{translateLabel('selectedRecords', resource.id, { selected: selectedRecords.length })}
</Title>
<ButtonGroup size="sm" rounded buttons={bulkButtons} />
</Box>
</TableCaption>
)
}
Expand Down
7 changes: 2 additions & 5 deletions src/frontend/hooks/use-action/use-action.ts
@@ -1,4 +1,4 @@
import { useLocation, useHistory } from 'react-router'
import { useHistory } from 'react-router'

import { ActionResponse } from '../../../backend/actions/action.interface'

Expand All @@ -25,25 +25,22 @@ export function useAction<K extends ActionResponse>(
params: DifferentActionParams,
onActionCall?: ActionCallCallback,
): UseActionResult<K> {
const location = useLocation()
const history = useHistory()

const actionResponseHandler = useActionResponseHandler(onActionCall)

const href = actionHref(action, params, location.search)
const href = actionHref(action, params)

const callApi = buildActionCallApiTrigger<K>({
action,
params,
actionResponseHandler,
search: location.search,
})

const handleClick = buildActionClickHandler({
action,
params,
actionResponseHandler,
search: location.search,
push: history.push,
})

Expand Down
4 changes: 0 additions & 4 deletions src/frontend/interfaces/action/action-href.ts
Expand Up @@ -7,7 +7,6 @@ const h = new ViewHelpers()
export const actionHref = (
action: ActionJSON,
params: DifferentActionParams,
search?: Location['search'],
): string | null => {
const actionName = action.name

Expand All @@ -19,17 +18,14 @@ export const actionHref = (
record: (): string => h.recordActionUrl({
...params as RecordActionParams,
actionName,
search,
}),
resource: (): string => h.resourceActionUrl({
resourceId: params.resourceId,
actionName,
search,
}),
bulk: (): string => h.bulkActionUrl({
...params,
actionName,
search,
}),
}
if (hrefMap[action.actionType]) {
Expand Down
9 changes: 5 additions & 4 deletions src/frontend/interfaces/action/build-action-click-handler.ts
Expand Up @@ -12,24 +12,25 @@ export type BuildActionClickOptions = {
params: DifferentActionParams;
actionResponseHandler: ReturnType<typeof useActionResponseHandler>;
push: (path: string, state?: any) => void;
search?: Location['search'];
}

export type BuildActionClickReturn = (event: any) => any

export const buildActionClickHandler = (
options: BuildActionClickOptions,
): BuildActionClickReturn => {
const { action, params, actionResponseHandler, search, push } = options
const { action, params, actionResponseHandler, push } = options

const handleActionClick = (event: React.MouseEvent<HTMLElement>): void => {
event.preventDefault()
event.stopPropagation()

const href = actionHref(action, params, search)
const href = actionHref(action, params)

console.log({ href })

const callApi = buildActionCallApiTrigger({
params, action, actionResponseHandler, search,
params, action, actionResponseHandler,
})

if (action.guard && !confirm(action.guard)) {
Expand Down

0 comments on commit 203268f

Please sign in to comment.