Skip to content

Commit 4e612d7

Browse files
authored
Refactor CheckboxList: style and alignment (#742)
* refactor(CheckboxList): manage button version alignment with the secondary action. Remove unecessary margin and padding when needed Signed-off-by: sBouzols <[email protected]>
1 parent f604af2 commit 4e612d7

12 files changed

+242
-282
lines changed

demo/src/app.jsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Box,
1111
Button,
1212
Checkbox,
13+
Chip,
1314
createTheme,
1415
CssBaseline,
1516
FormControlLabel,
@@ -22,6 +23,7 @@ import {
2223
Tabs,
2324
TextField,
2425
ThemeProvider,
26+
Tooltip,
2527
Typography,
2628
} from '@mui/material';
2729
import { Comment as CommentIcon } from '@mui/icons-material';
@@ -555,12 +557,39 @@ function AppContent({ language, onLanguageClick }) {
555557

556558
const [checkBoxListOption, setCheckBoxListOption] = useState([
557559
{ id: 'kiki', label: 'Kylian Mbappe' },
558-
{ id: 'ney', label: 'Neymar' },
560+
{
561+
id: 'ney',
562+
label: 'Neymar',
563+
labelSecondary: (
564+
<Tooltip title="this is the Chip tooltip">
565+
<Chip
566+
onClick={(e) => {
567+
e.stopPropagation();
568+
console.log("Chip click doesn't proc click in list item");
569+
}}
570+
size="small"
571+
label="GOAT"
572+
/>
573+
</Tooltip>
574+
),
575+
},
559576
{ id: 'lapulga', label: 'Lionel Messi' },
560577
{ id: 'ibra', label: 'Zlatan Ibrahimovic' },
561578
{
562579
id: 'john',
563580
label: 'Johannes Vennegoor of Hesselink is the football player with the longest name in history',
581+
labelSecondary: (
582+
<Tooltip title="this is the Chip tooltip">
583+
<Chip
584+
onClick={(e) => {
585+
e.stopPropagation();
586+
console.log("Chip click doesn't proc click in list item");
587+
}}
588+
size="small"
589+
label="GOAT"
590+
/>
591+
</Tooltip>
592+
),
564593
},
565594
]);
566595

@@ -602,13 +631,16 @@ function AppContent({ language, onLanguageClick }) {
602631
selectedItems={[]}
603632
open={openMultiChoiceDialog}
604633
getItemLabel={(o) => o.label}
634+
getItemLabelSecondary={(o) => o.labelSecondary}
605635
getItemId={(o) => o.id}
606636
handleClose={() => setOpenMultiChoiceDialog(false)}
607637
handleValidate={() => setOpenMultiChoiceDialog(false)}
608638
titleId="Checkbox list"
609639
divider
610640
secondaryAction={secondaryAction}
611641
addSelectAllCheckbox
642+
onItemClick={(item) => console.log('clicked', item)}
643+
isItemClickable={(item) => item.id === 'ney' || item.id === 'john'}
612644
/>
613645

614646
<Button
@@ -626,13 +658,14 @@ function AppContent({ language, onLanguageClick }) {
626658
selectedItems={[]}
627659
open={openDraggableMultiChoiceDialog}
628660
getItemLabel={(o) => o.label}
661+
getItemLabelSecondary={(o) => o.labelSecondary}
629662
getItemId={(o) => o.id}
630663
handleClose={() => setOpenDraggableMultiChoiceDialog(false)}
631664
handleValidate={() => setOpenDraggableMultiChoiceDialog(false)}
632665
titleId="Draggable checkbox list"
633666
divider
634667
secondaryAction={secondaryAction}
635-
isDndDragAndDropActive
668+
isDndActive
636669
onDragEnd={({ source, destination }) => {
637670
if (destination !== null && source.index !== destination.index) {
638671
const res = [...checkBoxListOption];
@@ -641,6 +674,7 @@ function AppContent({ language, onLanguageClick }) {
641674
setCheckBoxListOption(res);
642675
}
643676
}}
677+
addSelectAllCheckbox
644678
onItemClick={(item) => console.log('clicked', item)}
645679
isItemClickable={(item) => item.id.indexOf('i') >= 0}
646680
sx={{

src/components/checkBoxList/CheckBoxList.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { CheckBoxListItems } from './CheckBoxListItems';
1212
import { CheckboxListProps } from './checkBoxList.type';
1313

1414
export function CheckBoxList<T>({
15-
isDndDragAndDropActive = false,
15+
isDndActive = false,
1616
onDragStart,
1717
onDragEnd,
1818
isDragDisable = false,
@@ -22,15 +22,10 @@ export function CheckBoxList<T>({
2222
const [isDragging, setIsDragging] = useState(false);
2323

2424
const checkBoxField = (
25-
<CheckBoxListItems
26-
isDndDragAndDropActive={isDndDragAndDropActive}
27-
isDragDisable={isDragDisable || isDragging}
28-
sx={sx}
29-
{...props}
30-
/>
25+
<CheckBoxListItems isDndActive={isDndActive} isDragDisable={isDragDisable || isDragging} sx={sx} {...props} />
3126
);
3227

33-
return isDndDragAndDropActive ? (
28+
return isDndActive ? (
3429
<DragDropContext
3530
onDragEnd={(dropResult) => {
3631
if (onDragEnd) {

src/components/checkBoxList/CheckBoxListItem.tsx

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,29 @@
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
*/
77

8-
import { useState } from 'react';
9-
import { ListItem } from '@mui/material';
8+
import { useCallback, useState } from 'react';
9+
import { ListItem, ListItemButton } from '@mui/material';
1010
import { CheckBoxListItemProps } from './checkBoxList.type';
11-
import { ClickableCheckBoxItem } from './ClickableCheckBoxItem';
12-
import { ClickableRowItem } from './ClickableRowItem';
11+
import { CheckBoxListItemContent } from './CheckBoxListItemContent';
12+
import { mergeSx } from '../../utils';
13+
14+
const styles = {
15+
checkboxListItem: {
16+
alignItems: 'flex-start',
17+
// this is the only way to unset the absolute positionning of the secondary action
18+
'& .MuiListItemSecondaryAction-root': {
19+
marginTop: '1px',
20+
position: 'relative',
21+
top: 0,
22+
right: 0,
23+
transform: 'none',
24+
},
25+
// this is the only way to unset a 48px right padding when ListItemButton is hovered
26+
'& .MuiListItemButton-root': {
27+
paddingRight: '0px',
28+
},
29+
},
30+
};
1331

1432
export function CheckBoxListItem<T>({
1533
item,
@@ -19,28 +37,43 @@ export function CheckBoxListItem<T>({
1937
divider,
2038
onItemClick,
2139
isItemClickable,
40+
disabled,
2241
...props
2342
}: Readonly<CheckBoxListItemProps<T>>) {
2443
const [hover, setHover] = useState<string>('');
44+
const handleItemClick = useCallback(() => {
45+
if (!onItemClick) {
46+
return; // nothing to do
47+
}
48+
if (isItemClickable) {
49+
if (isItemClickable(item)) {
50+
onItemClick(item); // only call on clickable items
51+
}
52+
return;
53+
}
54+
// otherwise, every items are clickable
55+
onItemClick(item);
56+
}, [isItemClickable, item, onItemClick]);
2557
return (
2658
<ListItem
2759
secondaryAction={secondaryAction?.(item, hover)}
28-
sx={{ minWidth: 0, ...sx?.checkboxListItem }}
60+
sx={mergeSx(styles.checkboxListItem, sx?.checkboxListItem)}
2961
onMouseEnter={() => setHover(getItemId(item))}
3062
onMouseLeave={() => setHover('')}
31-
disablePadding={!!onItemClick}
32-
disableGutters
63+
disablePadding
3364
divider={divider}
3465
>
35-
{!onItemClick ? (
36-
<ClickableCheckBoxItem sx={sx} {...props} />
66+
{onItemClick ? (
67+
<ListItemButton
68+
// this is to align checkbox and label
69+
sx={mergeSx({ alignItems: 'flex-start', padding: 'unset' }, sx?.checkboxButton)}
70+
disabled={disabled}
71+
onClick={handleItemClick}
72+
>
73+
<CheckBoxListItemContent sx={sx} {...props} />
74+
</ListItemButton>
3775
) : (
38-
<ClickableRowItem
39-
isItemClickable={isItemClickable?.(item)}
40-
onItemClick={() => onItemClick(item)}
41-
sx={sx}
42-
{...props}
43-
/>
76+
<CheckBoxListItemContent sx={sx} {...props} />
4477
)}
4578
</ListItem>
4679
);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
import { Checkbox, ListItemIcon, ListItemText } from '@mui/material';
8+
import { OverflowableText } from '../overflowableText';
9+
import { CheckBoxListItemContentProps } from './checkBoxList.type';
10+
import { mergeSx } from '../../utils';
11+
12+
export function CheckBoxListItemContent({
13+
sx,
14+
label,
15+
secondary,
16+
onClick,
17+
...props
18+
}: Readonly<CheckBoxListItemContentProps>) {
19+
const onCheckboxClick = (event: React.MouseEvent<HTMLButtonElement>) => {
20+
event.stopPropagation();
21+
onClick();
22+
};
23+
return (
24+
<>
25+
<ListItemIcon sx={mergeSx({ marginTop: '0px' }, sx?.checkBoxIcon)}>
26+
<Checkbox sx={sx?.checkbox} disableRipple onClick={onCheckboxClick} {...props} />
27+
</ListItemIcon>
28+
<ListItemText
29+
sx={mergeSx(
30+
{
31+
display: 'flex',
32+
flexDirection: 'column',
33+
alignItems: 'flex-start',
34+
paddingTop: '0px', // this is to align text with default padding/margin of the checkbox
35+
marginTop: '9px',
36+
},
37+
sx?.listItemText
38+
)}
39+
disableTypography
40+
secondary={secondary}
41+
>
42+
<OverflowableText sx={mergeSx({ width: '100%' }, sx?.label)} text={label} />
43+
</ListItemText>
44+
</>
45+
);
46+
}

src/components/checkBoxList/CheckBoxListItems.tsx

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
*/
77

88
import { useCallback, useMemo } from 'react';
9-
import { Checkbox, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
9+
import { List, ListItem, ListItemButton } from '@mui/material';
1010
import { FormattedMessage } from 'react-intl';
1111
import { Draggable } from '@hello-pangea/dnd';
1212
import { CheckBoxListItem } from './CheckBoxListItem';
13-
import { OverflowableText } from '../overflowableText';
1413
import { DraggableCheckBoxListItem } from './DraggableCheckBoxListItem';
1514
import { CheckBoxListItemsProps } from './checkBoxList.type';
15+
import { CheckBoxListItemContent } from './CheckBoxListItemContent';
1616

1717
export function CheckBoxListItems<T>({
1818
items,
@@ -24,8 +24,9 @@ export function CheckBoxListItems<T>({
2424
addSelectAllCheckbox,
2525
selectAllCheckBoxLabel,
2626
getItemLabel,
27+
getItemLabelSecondary,
2728
isDisabled,
28-
isDndDragAndDropActive,
29+
isDndActive,
2930
isDragDisable,
3031
divider,
3132
onItemClick,
@@ -98,30 +99,28 @@ export function CheckBoxListItems<T>({
9899
},
99100
}}
100101
>
101-
<ListItemButton onClick={toggleSelectAll} sx={{ paddingLeft: 0 }}>
102-
<ListItemIcon sx={{ minWidth: 0 }}>
103-
<Checkbox
104-
sx={{ paddingLeft: 0 }}
105-
checked={selectedItems.length !== 0}
106-
indeterminate={selectedItems.length > 0 && selectedItems.length !== items.length}
107-
/>
108-
</ListItemIcon>
109-
<ListItemText sx={{ display: 'flex' }} disableTypography>
110-
<OverflowableText
111-
text={<FormattedMessage id={selectAllLabel} defaultMessage={selectAllLabel} />}
112-
/>
113-
</ListItemText>
102+
<ListItemButton
103+
onClick={toggleSelectAll}
104+
sx={{ alignItems: 'flex-start', paddingLeft: isDndActive ? '24px' : 0 }}
105+
>
106+
<CheckBoxListItemContent
107+
onClick={toggleSelectAll}
108+
checked={selectedItems.length !== 0}
109+
indeterminate={selectedItems.length > 0 && selectedItems.length !== items.length}
110+
label={<FormattedMessage id={selectAllLabel} defaultMessage={selectAllLabel} />}
111+
/>
114112
</ListItemButton>
115113
</ListItem>
116114
)}
117115
{items?.map((item, index) => {
118116
const label = getItemLabel ? getItemLabel(item) : getItemId(item);
117+
const secondary = getItemLabelSecondary ? getItemLabelSecondary(item) : null;
119118
const disabled = isDisabled ? isDisabled(item) : false;
120119
const addDivider = divider && index < items.length - 1;
121120
// sx can be dependent on item or not
122121
const calculatedItemSx = typeof sx?.items === 'function' ? sx?.items(item) : sx?.items;
123122

124-
if (isDndDragAndDropActive) {
123+
if (isDndActive) {
125124
return (
126125
<Draggable
127126
draggableId={getItemId(item)}
@@ -135,6 +134,7 @@ export function CheckBoxListItems<T>({
135134
item={item}
136135
checked={isChecked(item)}
137136
label={label}
137+
secondary={secondary}
138138
onClick={() => toggleSelection(getItemId(item))}
139139
sx={calculatedItemSx}
140140
disabled={disabled}
@@ -156,6 +156,7 @@ export function CheckBoxListItems<T>({
156156
item={item}
157157
checked={isChecked(item)}
158158
label={label}
159+
secondary={secondary}
159160
onClick={() => toggleSelection(getItemId(item))}
160161
disabled={disabled}
161162
getItemId={getItemId}

src/components/checkBoxList/ClickableCheckBoxItem.tsx

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)