Enhance CompactChoreCard and MyChores with animated transitions for multi-select and search filter
This commit is contained in:
@@ -399,8 +399,8 @@ const CompactChoreCard = ({
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
position: 'relative',
|
||||
pl: isMultiSelectMode ? '48px' : '16px', // Add space for checkbox when in multi-select mode
|
||||
backgroundColor: 'background.surface',
|
||||
pl: '16px', // Consistent padding since both elements are in the same position
|
||||
// backgroundColor: 'background.surface',
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
'&:hover': {
|
||||
bgcolor: 'background.level1',
|
||||
@@ -428,37 +428,6 @@ const CompactChoreCard = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Multi-select checkbox */}
|
||||
{isMultiSelectMode && (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={onSelectionToggle}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: 16,
|
||||
zIndex: 2,
|
||||
bgcolor: 'background.surface',
|
||||
borderRadius: 'md',
|
||||
boxShadow: 'sm',
|
||||
border: '2px solid',
|
||||
borderColor: 'divider',
|
||||
'&:hover': {
|
||||
bgcolor: 'background.level1',
|
||||
borderColor: 'primary.300',
|
||||
},
|
||||
'&.Mui-checked': {
|
||||
bgcolor: 'primary.500',
|
||||
borderColor: 'primary.500',
|
||||
color: 'primary.solidColor',
|
||||
'&:hover': {
|
||||
bgcolor: 'primary.600',
|
||||
borderColor: 'primary.600',
|
||||
},
|
||||
},
|
||||
}}
|
||||
onClick={e => e.stopPropagation()}
|
||||
/>
|
||||
)}
|
||||
{/* Priority bar clickable area */}
|
||||
{chore.priority > 0 && (
|
||||
<Box
|
||||
@@ -477,48 +446,128 @@ const CompactChoreCard = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* Complete Button - Left side like typical task apps */}
|
||||
<IconButton
|
||||
variant='soft'
|
||||
color='success'
|
||||
size='sm'
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleTaskCompletion()
|
||||
}}
|
||||
disabled={isPendingCompletion || notInCompletionWindow(chore)}
|
||||
|
||||
{/* Animated transition container for Complete Button / Multi-select checkbox */}
|
||||
<Box
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: '50%',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 40,
|
||||
height: 40,
|
||||
mr: 1.5,
|
||||
flexShrink: 0,
|
||||
bgcolor: 'success.softBg',
|
||||
color: 'success.600',
|
||||
border: '1px solid',
|
||||
borderColor: 'success.200',
|
||||
transition: 'all 0.2s ease',
|
||||
'&:hover': {
|
||||
bgcolor: 'success.solidBg',
|
||||
color: 'success.solidColor',
|
||||
borderColor: 'success.400',
|
||||
transform: 'scale(1.05)',
|
||||
},
|
||||
'&:active': {
|
||||
transform: 'scale(0.95)',
|
||||
},
|
||||
'&:disabled': {
|
||||
opacity: 0.5,
|
||||
transform: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isPendingCompletion ? (
|
||||
<CircularProgress size='sm' />
|
||||
) : (
|
||||
<Check sx={{ fontSize: 16 }} />
|
||||
)}
|
||||
</IconButton>
|
||||
{/* Complete Button */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition:
|
||||
'opacity 0.3s ease-in-out, transform 0.3s ease-in-out',
|
||||
opacity: isMultiSelectMode ? 0 : 1,
|
||||
transform: isMultiSelectMode
|
||||
? 'scale(0.8) rotate(45deg)'
|
||||
: 'scale(1) rotate(0deg)',
|
||||
pointerEvents: isMultiSelectMode ? 'none' : 'auto',
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
variant='soft'
|
||||
color='success'
|
||||
size='sm'
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleTaskCompletion()
|
||||
}}
|
||||
disabled={isPendingCompletion || notInCompletionWindow(chore)}
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: '50%',
|
||||
bgcolor: 'success.softBg',
|
||||
color: 'success.600',
|
||||
border: '1px solid',
|
||||
borderColor: 'success.200',
|
||||
transition: 'all 0.2s ease',
|
||||
'&:hover': {
|
||||
bgcolor: 'success.solidBg',
|
||||
color: 'success.solidColor',
|
||||
borderColor: 'success.400',
|
||||
transform: 'scale(1.05)',
|
||||
},
|
||||
'&:active': {
|
||||
transform: 'scale(0.95)',
|
||||
},
|
||||
'&:disabled': {
|
||||
opacity: 0.5,
|
||||
transform: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isPendingCompletion ? (
|
||||
<CircularProgress size='sm' />
|
||||
) : (
|
||||
<Check sx={{ fontSize: 16 }} />
|
||||
)}
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
{/* Multi-select Checkbox */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition:
|
||||
'opacity 0.3s ease-in-out, transform 0.3s ease-in-out',
|
||||
opacity: isMultiSelectMode ? 1 : 0,
|
||||
transform: isMultiSelectMode
|
||||
? 'scale(1) rotate(0deg)'
|
||||
: 'scale(0.8) rotate(-45deg)',
|
||||
pointerEvents: isMultiSelectMode ? 'auto' : 'none',
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={onSelectionToggle}
|
||||
sx={{
|
||||
bgcolor: 'background.surface',
|
||||
borderRadius: 'md',
|
||||
boxShadow: 'sm',
|
||||
border: '2px solid',
|
||||
borderColor: 'divider',
|
||||
'&:hover': {
|
||||
bgcolor: 'background.level1',
|
||||
borderColor: 'primary.300',
|
||||
},
|
||||
'&.Mui-checked': {
|
||||
bgcolor: 'primary.500',
|
||||
borderColor: 'primary.500',
|
||||
color: 'primary.solidColor',
|
||||
'&:hover': {
|
||||
bgcolor: 'primary.600',
|
||||
borderColor: 'primary.600',
|
||||
},
|
||||
},
|
||||
}}
|
||||
onClick={e => e.stopPropagation()}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Content - Center */}
|
||||
<Box
|
||||
@@ -633,31 +682,46 @@ const CompactChoreCard = ({
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Right side - Action Menu only */}
|
||||
<ChoreActionMenu
|
||||
variant='plain'
|
||||
chore={chore}
|
||||
onChoreUpdate={onChoreUpdate}
|
||||
onChoreRemove={onChoreRemove}
|
||||
onCompleteWithNote={() => setIsCompleteWithNoteModalOpen(true)}
|
||||
onCompleteWithPastDate={() =>
|
||||
setIsCompleteWithPastDateModalOpen(true)
|
||||
}
|
||||
onChangeAssignee={() => setIsChangeAssigneeModalOpen(true)}
|
||||
onChangeDueDate={() => setIsChangeDueDateModalOpen(true)}
|
||||
onWriteNFC={() => setIsNFCModalOpen(true)}
|
||||
onDelete={handleDelete}
|
||||
{/* Right side - Action Menu with animation */}
|
||||
<Box
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: 'text.tertiary',
|
||||
flexShrink: 0,
|
||||
'&:hover': {
|
||||
color: 'text.secondary',
|
||||
bgcolor: 'background.level1',
|
||||
},
|
||||
transition:
|
||||
'opacity 0.3s ease-in-out, transform 0.3s ease-in-out, width 0.3s ease-in-out, margin 0.3s ease-in-out',
|
||||
opacity: isMultiSelectMode ? 0 : 1,
|
||||
transform: isMultiSelectMode
|
||||
? 'translateX(20px) scale(0.8)'
|
||||
: 'translateX(0) scale(1)',
|
||||
width: isMultiSelectMode ? 0 : 32,
|
||||
marginRight: isMultiSelectMode ? 0 : undefined,
|
||||
overflow: 'hidden',
|
||||
pointerEvents: isMultiSelectMode ? 'none' : 'auto',
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<ChoreActionMenu
|
||||
variant='plain'
|
||||
chore={chore}
|
||||
onChoreUpdate={onChoreUpdate}
|
||||
onChoreRemove={onChoreRemove}
|
||||
onCompleteWithNote={() => setIsCompleteWithNoteModalOpen(true)}
|
||||
onCompleteWithPastDate={() =>
|
||||
setIsCompleteWithPastDateModalOpen(true)
|
||||
}
|
||||
onChangeAssignee={() => setIsChangeAssigneeModalOpen(true)}
|
||||
onChangeDueDate={() => setIsChangeDueDateModalOpen(true)}
|
||||
onWriteNFC={() => setIsNFCModalOpen(true)}
|
||||
onDelete={handleDelete}
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: 'text.tertiary',
|
||||
flexShrink: 0,
|
||||
'&:hover': {
|
||||
color: 'text.secondary',
|
||||
bgcolor: 'background.level1',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* All modals (same as original) */}
|
||||
|
||||
@@ -982,7 +982,18 @@ const MyChores = () => {
|
||||
{isMultiSelectMode ? <CheckBox /> : <CheckBoxOutlineBlank />}
|
||||
</IconButton>
|
||||
</Box>
|
||||
{showSearchFilter && (
|
||||
|
||||
{/* Search Filter with animation */}
|
||||
<Box
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
transition: 'all 0.3s ease-in-out',
|
||||
maxHeight: showSearchFilter ? '150px' : '0',
|
||||
opacity: showSearchFilter ? 1 : 0,
|
||||
transform: showSearchFilter ? 'translateY(0)' : 'translateY(-10px)',
|
||||
marginBottom: showSearchFilter ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<div className='flex gap-4'>
|
||||
<div className='grid flex-1 grid-cols-3 gap-4'>
|
||||
<IconButtonWithMenu
|
||||
@@ -1101,20 +1112,30 @@ const MyChores = () => {
|
||||
<CancelRounded />
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Multi-select Toolbar */}
|
||||
{isMultiSelectMode && (
|
||||
{/* Multi-select Toolbar with animation */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 1000,
|
||||
overflow: 'hidden',
|
||||
transition: 'all 0.3s ease-in-out',
|
||||
maxHeight: isMultiSelectMode ? '200px' : '0',
|
||||
opacity: isMultiSelectMode ? 1 : 0,
|
||||
transform: isMultiSelectMode
|
||||
? 'translateY(0)'
|
||||
: 'translateY(-20px)',
|
||||
marginBottom: isMultiSelectMode ? 2 : 0,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 1000,
|
||||
backgroundColor: 'background.surface',
|
||||
backdropFilter: 'blur(8px)',
|
||||
borderRadius: 'lg',
|
||||
p: 2,
|
||||
mb: 2,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
boxShadow: 'm',
|
||||
@@ -1300,7 +1321,7 @@ const MyChores = () => {
|
||||
</IconButton> */}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{searchFilter !== 'All' && (
|
||||
<Chip
|
||||
|
||||
@@ -384,7 +384,7 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => {
|
||||
return (
|
||||
<Modal open={isModalOpen} onClose={handleCloseModal}>
|
||||
<ModalOverflow>
|
||||
<ModalDialog size='lg' sx={{ minWidth: '80%' }}>
|
||||
<ModalDialog size='lg' sx={{ minWidth: '100%' }}>
|
||||
<Typography level='h4'>Create new task</Typography>
|
||||
<Chip startDecorator='🚧' variant='soft' color='warning' size='sm'>
|
||||
Experimental Feature
|
||||
|
||||
Reference in New Issue
Block a user