Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/module/src/BulkSelect/BulkSelect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,35 @@ describe('BulkSelect component', () => {
expect(toggleWrapper).toBeInTheDocument();
expect(toggleWrapper).toHaveClass('custom-menu-toggle');
});

test('should render with custom i18n labels', async () => {
const user = userEvent.setup();
render(
<BulkSelect
canSelectAll
pageCount={5}
totalCount={10}
selectedCount={2}
pageSelected={false}
pagePartiallySelected={true}
onSelect={() => null}
selectNoneLabel="Aucune sélection (0)"
selectPageLabel={(pageCount) => `Sélectionner la page${pageCount ? ` (${pageCount})` : ''}`}
selectAllLabel={(totalCount) => `Tout sélectionner${totalCount ? ` (${totalCount})` : ''}`}
selectedLabel={(selectedCount) => `${selectedCount} sélectionné${selectedCount > 1 ? 's' : ''}`}
/>
);

// Check custom selected label
expect(screen.getByText('2 sélectionnés')).toBeInTheDocument();

// Open the dropdown to check option labels
const toggleButton = screen.getByLabelText('Bulk select toggle');
await user.click(toggleButton);

// Check custom dropdown labels
expect(screen.getByText('Aucune sélection (0)')).toBeInTheDocument();
expect(screen.getByText('Sélectionner la page (5)')).toBeInTheDocument();
expect(screen.getByText('Tout sélectionner (10)')).toBeInTheDocument();
});
});
28 changes: 23 additions & 5 deletions packages/module/src/BulkSelect/BulkSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export const BulkSelectValue = {

export type BulkSelectValue = (typeof BulkSelectValue)[keyof typeof BulkSelectValue];

const defaultSelectPageLabel = (pageCount?: number) => `Select page${pageCount ? ` (${pageCount})` : ''}`;
const defaultSelectAllLabel = (totalCount?: number) => `Select all${totalCount ? ` (${totalCount})` : ''}`;
const defaultSelectedLabel = (selectedCount: number) => `${selectedCount} selected`;

/** extends DropdownProps */
export interface BulkSelectProps extends Omit<DropdownProps, 'toggle' | 'onSelect'> {
/** BulkSelect className */
Expand Down Expand Up @@ -50,6 +54,14 @@ export interface BulkSelectProps extends Omit<DropdownProps, 'toggle' | 'onSelec
dropdownListProps?: Omit<DropdownListProps, 'children'>;
/** Additional props for MenuToggleProps */
menuToggleProps?: Omit<MenuToggleProps, 'children' | 'splitButtonItems' | 'ref' | 'isExpanded' | 'onClick'>;
/** Custom label for "Select none" option. Defaults to "Select none (0)" */
selectNoneLabel?: string;
/** Custom label for "Select page" option. Receives pageCount as parameter. Defaults to "Select page (N)" */
selectPageLabel?: (pageCount?: number) => string;
/** Custom label for "Select all" option. Receives totalCount as parameter. Defaults to "Select all (N)" */
selectAllLabel?: (totalCount?: number) => string;
/** Custom label formatter for selected count. Receives selectedCount as parameter. Defaults to "N selected" */
selectedLabel?: (selectedCount: number) => string;
}

export const BulkSelect: FC<BulkSelectProps> = ({
Expand All @@ -65,6 +77,10 @@ export const BulkSelect: FC<BulkSelectProps> = ({
menuToggleCheckboxProps,
dropdownListProps,
menuToggleProps,
selectNoneLabel = 'Select none (0)',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue: useMemo invalidation on every render.

The default values for selectPageLabel, selectAllLabel, and selectedLabel are inline arrow functions in the destructuring:

  selectPageLabel = (pageCount?: number) => `Select page${pageCount ? ` (${pageCount})` : ''}`,                                
  selectAllLabel = (totalCount?: number) => `Select all${totalCount ? ` (${totalCount})` : ''}`,            

These create new function references on every render, even when the consumer doesn't pass custom labels. Since they're added
to the useMemo dependency array, the memoization of dropdown items is effectively broken — it will recompute every render
instead of only when pageCount/totalCount change.

Fix: Hoist the defaults to module-level constants:

  const defaultSelectPageLabel = (pageCount?: number) => `Select page${pageCount ? ` (${pageCount})` : ''}`;                   
  const defaultSelectAllLabel = (totalCount?: number) => `Select all${totalCount ? ` (${totalCount})` : ''}`;                  
  const defaultSelectedLabel = (count: number) => `${count} selected`;                                                         
                                                                                                                               
  export const BulkSelect: FC<BulkSelectProps> = ({                                                                            
    // ...other props                                                                                                          
    selectNoneLabel = 'Select none (0)',                                                                                       
    selectPageLabel = defaultSelectPageLabel,                                                                                  
    selectAllLabel = defaultSelectAllLabel,                                                                                    
    selectedLabel = defaultSelectedLabel,                                                                                      
    ...props                                                                                                                   
  }: BulkSelectProps) => {                                                                                                     
    // rest of component unchanged    

selectPageLabel = defaultSelectPageLabel,
selectAllLabel = defaultSelectAllLabel,
selectedLabel = defaultSelectedLabel,
...props
}: BulkSelectProps) => {
const [ isOpen, setOpen ] = useState(false);
Expand All @@ -73,23 +89,25 @@ export const BulkSelect: FC<BulkSelectProps> = ({
() => (
<>
<DropdownItem ouiaId={`${ouiaId}-select-none`} value={BulkSelectValue.none} key={BulkSelectValue.none}>
Select none (0)
{selectNoneLabel}
</DropdownItem>
{isDataPaginated && (
<DropdownItem ouiaId={`${ouiaId}-select-page`} value={BulkSelectValue.page} key={BulkSelectValue.page}>
{`Select page${pageCount ? ` (${pageCount})` : ''}`}
{selectPageLabel(pageCount)}
</DropdownItem>
)}
{canSelectAll && (
<DropdownItem ouiaId={`${ouiaId}-select-all`} value={BulkSelectValue.all} key={BulkSelectValue.all}>
{`Select all${totalCount ? ` (${totalCount})` : ''}`}
{selectAllLabel(totalCount)}
</DropdownItem>
)}
</>
),
[ isDataPaginated, canSelectAll, ouiaId, pageCount, totalCount ]
[ isDataPaginated, canSelectAll, ouiaId, selectNoneLabel, selectPageLabel, selectAllLabel, pageCount, totalCount ]
);

const selectedLabelText = selectedLabel(selectedCount);

const allOption = isDataPaginated ? BulkSelectValue.page : BulkSelectValue.all;
const noneOption = isDataPaginated ? BulkSelectValue.nonePage : BulkSelectValue.none;

Expand Down Expand Up @@ -129,7 +147,7 @@ export const BulkSelect: FC<BulkSelectProps> = ({
>
{selectedCount > 0 ? (
<span data-ouia-component-id={`${ouiaId}-text`}>
{`${selectedCount} selected`}
{selectedLabelText}
</span>
) : null}
</MenuToggleCheckbox>
Expand Down
Loading