Skip to content

Commit

Permalink
refactor(ui): moves bulk edit controls (#11332)
Browse files Browse the repository at this point in the history
Bulk edit controls are currently displayed within the search bar of the
list view. This doesn't make sense from a UX perspective, as the current
selection is displayed somewhere else entirely. These controls also take
up a lot of visual real estate which is beginning to get overused
especially after the introduction of "list menu items" in #11230, and
the potential introduction of "saved filters" controls in #11330.

Now, they are rendered contextually _alongside_ the selection count. To
make room for these new controls, they are displayed in plain text and
the entity labels have been removed from the selection count.
  • Loading branch information
jacobsfletch authored Feb 21, 2025
1 parent a8bec9a commit f31568c
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 81 deletions.
16 changes: 16 additions & 0 deletions packages/ui/src/elements/DeleteMany/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,21 @@
align-items: center;
justify-content: center;
height: 100%;

&__toggle {
font-size: inherit;
line-height: inherit;
display: inline-flex;
background: transparent;
color: var(--theme-elevation-800);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: 0;
padding: 0;
align-items: center;
cursor: pointer;
text-decoration: underline;
}
}
}
6 changes: 3 additions & 3 deletions packages/ui/src/elements/DeleteMany/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { useTranslation } from '../../providers/Translation/index.js'
import { requests } from '../../utilities/api.js'
import { mergeListSearchAndWhere } from '../../utilities/mergeListSearchAndWhere.js'
import { ConfirmationModal } from '../ConfirmationModal/index.js'
import { Pill } from '../Pill/index.js'
import './index.scss'

const baseClass = 'delete-documents'
Expand Down Expand Up @@ -140,14 +139,15 @@ export const DeleteMany: React.FC<Props> = (props) => {

return (
<React.Fragment>
<Pill
<button
className={`${baseClass}__toggle`}
onClick={() => {
openModal(modalSlug)
}}
type="button"
>
{t('general:delete')}
</Pill>
</button>
<ConfirmationModal
body={t('general:aboutToDeleteCount', {
count,
Expand Down
24 changes: 5 additions & 19 deletions packages/ui/src/elements/EditMany/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,19 @@
@layer payload-default {
.edit-many {
&__toggle {
font-size: 1rem;
line-height: base(1.2);
font-size: inherit;
line-height: inherit;
display: inline-flex;
background: var(--theme-elevation-150);
background: transparent;
color: var(--theme-elevation-800);
border-radius: $style-radius-s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: 0;
padding: 0 base(0.4);
padding: 0;
align-items: center;
cursor: pointer;
text-decoration: none;

&:active,
&:focus {
outline: none;
}

&:hover {
background: var(--theme-elevation-100);
}

&:active {
background: var(--theme-elevation-100);
}
text-decoration: underline;
}

&__form {
Expand Down
13 changes: 8 additions & 5 deletions packages/ui/src/elements/EditMany/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
'use client'
import type { ClientCollectionConfig, FieldWithPathClient } from 'payload'

import { useModal } from '@faceless-ui/modal'
import React, { useState } from 'react'

import { useAuth } from '../../providers/Auth/index.js'
import { EditDepthProvider } from '../../providers/EditDepth/index.js'
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import './index.scss'
import { Drawer, DrawerToggler } from '../Drawer/index.js'
import { Drawer } from '../Drawer/index.js'
import { EditManyDrawerContent } from './DrawerContent.js'
import './index.scss'

export const baseClass = 'edit-many'

Expand All @@ -23,6 +24,7 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
} = props

const { permissions } = useAuth()
const { openModal } = useModal()

const { selectAll } = useSelection()
const { t } = useTranslation()
Expand All @@ -39,16 +41,17 @@ export const EditMany: React.FC<EditManyProps> = (props) => {

return (
<div className={baseClass}>
<DrawerToggler
<button
aria-label={t('general:edit')}
className={`${baseClass}__toggle`}
onClick={() => {
openModal(drawerSlug)
setSelected([])
}}
slug={drawerSlug}
type="button"
>
{t('general:edit')}
</DrawerToggler>
</button>
<EditDepthProvider>
<Drawer Header={null} slug={drawerSlug}>
<EditManyDrawerContent
Expand Down
30 changes: 10 additions & 20 deletions packages/ui/src/elements/ListControls/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ClientCollectionConfig, ResolvedFilterOptions, Where } from 'paylo

import { useWindowInfo } from '@faceless-ui/window-info'
import { getTranslation } from '@payloadcms/translations'
import React, { Fragment, useEffect, useRef, useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'

import { Popup, PopupList } from '../../elements/Popup/index.js'
import { useUseTitleField } from '../../hooks/useUseAsTitle.js'
Expand All @@ -14,12 +14,8 @@ import { useListQuery } from '../../providers/ListQuery/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { AnimateHeight } from '../AnimateHeight/index.js'
import { ColumnSelector } from '../ColumnSelector/index.js'
import { DeleteMany } from '../DeleteMany/index.js'
import { EditMany } from '../EditMany/index.js'
import { Pill } from '../Pill/index.js'
import { PublishMany } from '../PublishMany/index.js'
import { SearchFilter } from '../SearchFilter/index.js'
import { UnpublishMany } from '../UnpublishMany/index.js'
import { WhereBuilder } from '../WhereBuilder/index.js'
import validateWhereQuery from '../WhereBuilder/validateWhereQuery.js'
import { getTextFieldsToBeSearched } from './getTextFieldsToBeSearched.js'
Expand All @@ -31,7 +27,15 @@ export type ListControlsProps = {
readonly beforeActions?: React.ReactNode[]
readonly collectionConfig: ClientCollectionConfig
readonly collectionSlug: string
/**
* @deprecated
* These are now handled by the `ListSelection` component
*/
readonly disableBulkDelete?: boolean
/**
* @deprecated
* These are now handled by the `ListSelection` component
*/
readonly disableBulkEdit?: boolean
readonly enableColumns?: boolean
readonly enableSort?: boolean
Expand All @@ -53,8 +57,6 @@ export const ListControls: React.FC<ListControlsProps> = (props) => {
beforeActions,
collectionConfig,
collectionSlug,
disableBulkDelete,
disableBulkEdit,
enableColumns = true,
enableSort = false,
listMenuItems,
Expand Down Expand Up @@ -148,19 +150,7 @@ export const ListControls: React.FC<ListControlsProps> = (props) => {
/>
<div className={`${baseClass}__buttons`}>
<div className={`${baseClass}__buttons-wrap`}>
{!smallBreak && (
<React.Fragment>
{beforeActions && beforeActions}
{!disableBulkEdit && (
<Fragment>
<EditMany collection={collectionConfig} />
<PublishMany collection={collectionConfig} />
<UnpublishMany collection={collectionConfig} />
</Fragment>
)}
{!disableBulkDelete && <DeleteMany collection={collectionConfig} />}
</React.Fragment>
)}
{!smallBreak && <React.Fragment>{beforeActions && beforeActions}</React.Fragment>}
{enableColumns && (
<Pill
aria-controls={`${baseClass}-columns`}
Expand Down
44 changes: 32 additions & 12 deletions packages/ui/src/elements/ListSelection/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
'use client'
import type { ClientCollectionConfig } from 'payload'

import React, { Fragment } from 'react'

import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { DeleteMany } from '../DeleteMany/index.js'
import { EditMany } from '../EditMany/index.js'
import { PublishMany } from '../PublishMany/index.js'
import { UnpublishMany } from '../UnpublishMany/index.js'
import './index.scss'

const baseClass = 'list-selection'

export type ListSelectionProps = {
collectionConfig?: ClientCollectionConfig
disableBulkDelete?: boolean
disableBulkEdit?: boolean
label: string
}

export const ListSelection: React.FC<ListSelectionProps> = ({ label }) => {
export const ListSelection: React.FC<ListSelectionProps> = ({
collectionConfig,
disableBulkDelete,
disableBulkEdit,
label,
}) => {
const { count, selectAll, toggleAll, totalDocs } = useSelection()
const { t } = useTranslation()

Expand All @@ -21,21 +35,27 @@ export const ListSelection: React.FC<ListSelectionProps> = ({ label }) => {

return (
<div className={baseClass}>
<span>{t('general:selectedCount', { count, label })}</span>
<span>{t('general:selectedCount', { count, label: '' })}</span>
{selectAll !== SelectAllStatus.AllAvailable && count < totalDocs && (
<button
aria-label={t('general:selectAll', { count, label })}
className={`${baseClass}__button`}
id="select-all-across-pages"
onClick={() => toggleAll(true)}
type="button"
>
{t('general:selectAll', { count: totalDocs, label: '' })}
</button>
)}
{!disableBulkEdit && !disableBulkDelete && <span>&mdash;</span>}
{!disableBulkEdit && (
<Fragment>
<span>&mdash;</span>
<button
aria-label={t('general:selectAll', { count, label })}
className={`${baseClass}__button`}
id="select-all-across-pages"
onClick={() => toggleAll(true)}
type="button"
>
{t('general:selectAll', { count: totalDocs, label })}
</button>
<EditMany collection={collectionConfig} />
<PublishMany collection={collectionConfig} />
<UnpublishMany collection={collectionConfig} />
</Fragment>
)}
{!disableBulkDelete && <DeleteMany collection={collectionConfig} />}
</div>
)
}
21 changes: 21 additions & 0 deletions packages/ui/src/elements/PublishMany/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@import '../../scss/styles.scss';

@layer payload-default {
.publish-many {
&__toggle {
font-size: inherit;
line-height: inherit;
display: inline-flex;
background: transparent;
color: var(--theme-elevation-800);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: 0;
padding: 0;
align-items: center;
cursor: pointer;
text-decoration: underline;
}
}
}
7 changes: 4 additions & 3 deletions packages/ui/src/elements/PublishMany/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useTranslation } from '../../providers/Translation/index.js'
import { requests } from '../../utilities/api.js'
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
import { ConfirmationModal } from '../ConfirmationModal/index.js'
import { Pill } from '../Pill/index.js'
import './index.scss'

export type PublishManyProps = {
collection: ClientCollectionConfig
Expand Down Expand Up @@ -133,14 +133,15 @@ export const PublishMany: React.FC<PublishManyProps> = (props) => {

return (
<React.Fragment>
<Pill
<button
className={`${baseClass}__toggle`}
onClick={() => {
openModal(modalSlug)
}}
type="button"
>
{t('version:publish')}
</Pill>
</button>
<ConfirmationModal
body={t('version:aboutToPublishSelection', { label: getTranslation(plural, i18n) })}
cancelLabel={t('general:cancel')}
Expand Down
21 changes: 21 additions & 0 deletions packages/ui/src/elements/UnpublishMany/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@import '../../scss/styles.scss';

@layer payload-default {
.unpublish-many {
&__toggle {
font-size: inherit;
line-height: inherit;
display: inline-flex;
background: transparent;
color: var(--theme-elevation-800);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: 0;
padding: 0;
align-items: center;
cursor: pointer;
text-decoration: underline;
}
}
}
7 changes: 4 additions & 3 deletions packages/ui/src/elements/UnpublishMany/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useTranslation } from '../../providers/Translation/index.js'
import { requests } from '../../utilities/api.js'
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
import { ConfirmationModal } from '../ConfirmationModal/index.js'
import { Pill } from '../Pill/index.js'
import './index.scss'

export type UnpublishManyProps = {
collection: ClientCollectionConfig
Expand Down Expand Up @@ -130,14 +130,15 @@ export const UnpublishMany: React.FC<UnpublishManyProps> = (props) => {

return (
<React.Fragment>
<Pill
<button
className={`${baseClass}__toggle`}
onClick={() => {
toggleModal(modalSlug)
}}
type="button"
>
{t('version:unpublish')}
</Pill>
</button>
<ConfirmationModal
body={t('version:aboutToUnpublishSelection', { label: getTranslation(plural, i18n) })}
confirmingLabel={t('version:unpublishing')}
Expand Down
Loading

0 comments on commit f31568c

Please sign in to comment.