feat: add MoreVert icon button for toggle actions in multiple components

This commit is contained in:
Mo Tarbin
2026-02-15 23:21:44 -05:00
parent af401e7b27
commit 1fc1d69dae
6 changed files with 159 additions and 12 deletions

View File

@@ -14,11 +14,19 @@ import {
History, History,
Star, Star,
Timelapse, Timelapse,
TrendingUp, TrendingUp
} from '@mui/icons-material' } from '@mui/icons-material'
import DeleteIcon from '@mui/icons-material/Delete' import DeleteIcon from '@mui/icons-material/Delete'
import EditIcon from '@mui/icons-material/Edit' import EditIcon from '@mui/icons-material/Edit'
import { Box, Button, Card, Container, Grid, Sheet, Typography } from '@mui/joy' import {
Box,
Button,
Card,
Container,
Grid,
Sheet,
Typography
} from '@mui/joy'
import moment from 'moment' import moment from 'moment'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Link, useParams } from 'react-router-dom' import { Link, useParams } from 'react-router-dom'
@@ -42,6 +50,7 @@ const ChoreHistory = () => {
const [isEditModalOpen, setIsEditModalOpen] = useState(false) const [isEditModalOpen, setIsEditModalOpen] = useState(false)
const [editHistory, setEditHistory] = useState({}) const [editHistory, setEditHistory] = useState({})
const { confirmModalConfig, showConfirmation } = useConfirmationModal() const { confirmModalConfig, showConfirmation } = useConfirmationModal()
const [showMoreInfoId, setShowMoreInfoId] = useState(null)
// React Query hooks // React Query hooks
const { data: choreHistoryData, isLoading } = useChoreHistory(choreId) const { data: choreHistoryData, isLoading } = useChoreHistory(choreId)
@@ -298,6 +307,11 @@ const ChoreHistory = () => {
{choreHistory.map((historyEntry, index) => ( {choreHistory.map((historyEntry, index) => (
<SwipeableListItem <SwipeableListItem
key={historyEntry.id || index} key={historyEntry.id || index}
swipeActionOpen={
showMoreInfoId === (historyEntry.id || index)
? 'trailing'
: null
}
trailingActions={ trailingActions={
<TrailingActions> <TrailingActions>
<Box <Box
@@ -355,6 +369,14 @@ const ChoreHistory = () => {
performers={performers} performers={performers}
allHistory={choreHistory} allHistory={choreHistory}
index={index} index={index}
onToggleActions={() => {
const id = historyEntry.id || index
if (showMoreInfoId === id) {
setShowMoreInfoId(null)
} else {
setShowMoreInfoId(id)
}
}}
/> />
</SwipeableListItem> </SwipeableListItem>
))} ))}

View File

@@ -5,13 +5,14 @@ import {
CheckCircle, CheckCircle,
EventNote, EventNote,
HourglassEmpty, HourglassEmpty,
MoreVert,
Person, Person,
Redo, Redo,
ThumbDown, ThumbDown,
Timelapse, Timelapse,
Toll, Toll,
} from '@mui/icons-material' } from '@mui/icons-material'
import { Avatar, Box, Chip, Grid, Typography } from '@mui/joy' import { Avatar, Box, Chip, Grid, IconButton, Typography } from '@mui/joy'
import moment from 'moment' import moment from 'moment'
import { TASK_COLOR } from '../../utils/Colors.jsx' import { TASK_COLOR } from '../../utils/Colors.jsx'
@@ -85,7 +86,13 @@ const formatTime = seconds => {
/** /**
* Compact HistoryCard component - content only * Compact HistoryCard component - content only
*/ */
const HistoryCard = ({ allHistory, performers, historyEntry, index }) => { const HistoryCard = ({
allHistory,
performers,
historyEntry,
index,
onToggleActions,
}) => {
const performer = performers.find(p => p.userId === historyEntry.completedBy) const performer = performers.find(p => p.userId === historyEntry.completedBy)
const assignedTo = performers.find(p => p.userId === historyEntry.assignedTo) const assignedTo = performers.find(p => p.userId === historyEntry.assignedTo)
@@ -149,7 +156,7 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
borderColor: 'divider', borderColor: 'divider',
}} }}
> >
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1, minWidth: 0 }}>
<Grid container spacing={1} alignItems='center'> <Grid container spacing={1} alignItems='center'>
{/* First Row/Column: Status and Time Info */} {/* First Row/Column: Status and Time Info */}
<Grid xs={12} sm={8}> <Grid xs={12} sm={8}>
@@ -282,6 +289,21 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
</Grid> </Grid>
</Grid> </Grid>
</Box> </Box>
<Box>
{onToggleActions && (
<IconButton
color='neutral'
variant='plain'
size='sm'
onClick={e => {
e.stopPropagation()
onToggleActions()
}}
>
<MoreVert sx={{ fontSize: 18 }} />
</IconButton>
)}
</Box>
</Box> </Box>
) )
} }

View File

@@ -21,7 +21,7 @@ import {
TrailingActions, TrailingActions,
} from '@meauxt/react-swipeable-list' } from '@meauxt/react-swipeable-list'
import '@meauxt/react-swipeable-list/dist/styles.css' import '@meauxt/react-swipeable-list/dist/styles.css'
import { Add } from '@mui/icons-material' import { Add, MoreVert } from '@mui/icons-material'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { useUserProfile } from '../../queries/UserQueries' import { useUserProfile } from '../../queries/UserQueries'
import { getTextColorFromBackgroundColor } from '../../utils/Colors' import { getTextColorFromBackgroundColor } from '../../utils/Colors'
@@ -30,7 +30,7 @@ import { getSafeBottomStyles } from '../../utils/SafeAreaUtils'
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
import { useLabels } from './LabelQueries' import { useLabels } from './LabelQueries'
const LabelCardContent = ({ label, currentUserId }) => { const LabelCardContent = ({ label, currentUserId, onToggleActions }) => {
// Check if current user owns this label // Check if current user owns this label
const isOwnedByCurrentUser = label.created_by === currentUserId const isOwnedByCurrentUser = label.created_by === currentUserId
@@ -128,6 +128,21 @@ const LabelCardContent = ({ label, currentUserId }) => {
)} )}
</Box> </Box>
</Box> </Box>
<Box>
{onToggleActions && (
<IconButton
color='neutral'
variant='plain'
size='sm'
onClick={e => {
e.stopPropagation()
onToggleActions()
}}
>
<MoreVert sx={{ fontSize: 18 }} />
</IconButton>
)}
</Box>
</Box> </Box>
) )
} }
@@ -142,6 +157,7 @@ const LabelView = () => {
const [currentLabel, setCurrentLabel] = useState(null) const [currentLabel, setCurrentLabel] = useState(null)
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [confirmationModel, setConfirmationModel] = useState({}) const [confirmationModel, setConfirmationModel] = useState({})
const [showMoreInfoId, setShowMoreInfoId] = useState(null)
const handleAddLabel = () => { const handleAddLabel = () => {
setCurrentLabel(null) setCurrentLabel(null)
@@ -260,6 +276,7 @@ const LabelView = () => {
{userLabels.map(label => ( {userLabels.map(label => (
<SwipeableListItem <SwipeableListItem
key={label.id} key={label.id}
swipeActionOpen={showMoreInfoId === label.id ? 'trailing' : null}
trailingActions={ trailingActions={
<TrailingActions> <TrailingActions>
<Box <Box
@@ -311,7 +328,17 @@ const LabelView = () => {
</TrailingActions> </TrailingActions>
} }
> >
<LabelCardContent label={label} currentUserId={userProfile?.id} /> <LabelCardContent
label={label}
currentUserId={userProfile?.id}
onToggleActions={() => {
if (showMoreInfoId === label.id) {
setShowMoreInfoId(null)
} else {
setShowMoreInfoId(label.id)
}
}}
/>
</SwipeableListItem> </SwipeableListItem>
))} ))}
</SwipeableList> </SwipeableList>

View File

@@ -22,7 +22,7 @@ import {
TrailingActions, TrailingActions,
} from '@meauxt/react-swipeable-list' } from '@meauxt/react-swipeable-list'
import '@meauxt/react-swipeable-list/dist/styles.css' import '@meauxt/react-swipeable-list/dist/styles.css'
import { Add, Task } from '@mui/icons-material' import { Add, MoreVert, Task } from '@mui/icons-material'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { useChores } from '../../queries/ChoreQueries' import { useChores } from '../../queries/ChoreQueries'
import { useUserProfile } from '../../queries/UserQueries' import { useUserProfile } from '../../queries/UserQueries'
@@ -38,6 +38,7 @@ const ProjectCardContent = ({
currentUserId, currentUserId,
taskCounts = {}, taskCounts = {},
onCardClick, onCardClick,
onToggleActions,
}) => { }) => {
// Check if current user owns this project // Check if current user owns this project
const isOwnedByCurrentUser = project.created_by === currentUserId const isOwnedByCurrentUser = project.created_by === currentUserId
@@ -197,6 +198,21 @@ const ProjectCardContent = ({
)} )}
</Box> </Box>
</Box> </Box>
<Box>
{onToggleActions && (
<IconButton
color='neutral'
variant='plain'
size='sm'
onClick={e => {
e.stopPropagation()
onToggleActions()
}}
>
<MoreVert sx={{ fontSize: 18 }} />
</IconButton>
)}
</Box>
</Box> </Box>
) )
} }
@@ -215,6 +231,7 @@ const ProjectView = () => {
const [taskCounts, setTaskCounts] = useState({}) const [taskCounts, setTaskCounts] = useState({})
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [confirmationModel, setConfirmationModel] = useState({}) const [confirmationModel, setConfirmationModel] = useState({})
const [showMoreInfoId, setShowMoreInfoId] = useState(null)
const handleAddProject = () => { const handleAddProject = () => {
setCurrentProject(null) setCurrentProject(null)
@@ -373,6 +390,9 @@ const ProjectView = () => {
<SwipeableListItem <SwipeableListItem
onClick={() => handleCardClick(project)} onClick={() => handleCardClick(project)}
key={project.id} key={project.id}
swipeActionOpen={
showMoreInfoId === project.id ? 'trailing' : null
}
trailingActions={ trailingActions={
<TrailingActions> <TrailingActions>
<Box <Box
@@ -430,7 +450,13 @@ const ProjectView = () => {
project={project} project={project}
currentUserId={userProfile?.id} currentUserId={userProfile?.id}
taskCounts={taskCounts} taskCounts={taskCounts}
// onCardClick={} onToggleActions={() => {
if (showMoreInfoId === project.id) {
setShowMoreInfoId(null)
} else {
setShowMoreInfoId(project.id)
}
}}
/> />
</SwipeableListItem> </SwipeableListItem>
))} ))}

View File

@@ -11,6 +11,7 @@ import {
Delete, Delete,
Edit, Edit,
Flip, Flip,
MoreVert,
PlusOne, PlusOne,
ToggleOff, ToggleOff,
ToggleOn, ToggleOn,
@@ -40,7 +41,7 @@ import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
import CreateThingModal from '../Modals/Inputs/CreateThingModal' import CreateThingModal from '../Modals/Inputs/CreateThingModal'
import EditThingStateModal from '../Modals/Inputs/EditThingState' import EditThingStateModal from '../Modals/Inputs/EditThingState'
const ThingCardContent = ({ thing, onCardClick }) => { const ThingCardContent = ({ thing, onCardClick, onToggleActions }) => {
const getThingIcon = type => { const getThingIcon = type => {
if (type === 'text') { if (type === 'text') {
return <Flip /> return <Flip />
@@ -183,6 +184,21 @@ const ThingCardContent = ({ thing, onCardClick }) => {
</Chip> </Chip>
</Box> </Box>
</Box> </Box>
<Box>
{onToggleActions && (
<IconButton
color='neutral'
variant='plain'
size='sm'
onClick={e => {
e.stopPropagation()
onToggleActions()
}}
>
<MoreVert sx={{ fontSize: 18 }} />
</IconButton>
)}
</Box>
</Box> </Box>
) )
} }
@@ -194,6 +210,7 @@ const ThingsView = () => {
const [isShowEditThingStateModal, setIsShowEditStateModal] = useState(false) const [isShowEditThingStateModal, setIsShowEditStateModal] = useState(false)
const [createModalThing, setCreateModalThing] = useState(null) const [createModalThing, setCreateModalThing] = useState(null)
const [confirmModelConfig, setConfirmModelConfig] = useState({}) const [confirmModelConfig, setConfirmModelConfig] = useState({})
const [showMoreInfoId, setShowMoreInfoId] = useState(null)
const { showError, showNotification } = useNotification() const { showError, showNotification } = useNotification()
useEffect(() => { useEffect(() => {
@@ -388,6 +405,7 @@ const ThingsView = () => {
<SwipeableListItem <SwipeableListItem
onClick={() => navigate(`/things/${thing?.id}`)} onClick={() => navigate(`/things/${thing?.id}`)}
key={thing.id} key={thing.id}
swipeActionOpen={showMoreInfoId === thing.id ? 'trailing' : null}
trailingActions={ trailingActions={
<TrailingActions> <TrailingActions>
<Box <Box
@@ -474,7 +492,16 @@ const ThingsView = () => {
</TrailingActions> </TrailingActions>
} }
> >
<ThingCardContent thing={thing} /> <ThingCardContent
thing={thing}
onToggleActions={() => {
if (showMoreInfoId === thing.id) {
setShowMoreInfoId(null)
} else {
setShowMoreInfoId(thing.id)
}
}}
/>
</SwipeableListItem> </SwipeableListItem>
))} ))}
</SwipeableList> </SwipeableList>

View File

@@ -12,6 +12,7 @@ import {
BrowseGallery, BrowseGallery,
Delete, Delete,
Edit, Edit,
MoreVert,
PauseCircle, PauseCircle,
Person, Person,
PlayArrow, PlayArrow,
@@ -56,6 +57,7 @@ const TimerDetails = () => {
const [editingSessions, setEditingSessions] = useState({}) const [editingSessions, setEditingSessions] = useState({})
const [currentTime, setCurrentTime] = useState(new Date()) const [currentTime, setCurrentTime] = useState(new Date())
const [timerActionLoading, setTimerActionLoading] = useState(false) const [timerActionLoading, setTimerActionLoading] = useState(false)
const [showMoreInfoId, setShowMoreInfoId] = useState(null)
const { showError, showSuccess } = useNotification() const { showError, showSuccess } = useNotification()
// Fetch circle members data // Fetch circle members data
@@ -940,6 +942,11 @@ const TimerDetails = () => {
return ( return (
<SwipeableListItem <SwipeableListItem
key={pauseIndex} key={pauseIndex}
swipeActionOpen={
showMoreInfoId === pauseIndex
? 'trailing'
: null
}
trailingActions={ trailingActions={
<TrailingActions> <TrailingActions>
<Box <Box
@@ -1131,6 +1138,22 @@ const TimerDetails = () => {
{endTime ? `${endTime}` : '→ ongoing'} {endTime ? `${endTime}` : '→ ongoing'}
</Typography> </Typography>
</Box> </Box>
<IconButton
color='neutral'
variant='plain'
size='sm'
onClick={e => {
e.stopPropagation()
if (showMoreInfoId === pauseIndex) {
setShowMoreInfoId(null)
} else {
setShowMoreInfoId(pauseIndex)
}
}}
>
<MoreVert sx={{ fontSize: 18 }} />
</IconButton>
</Card> </Card>
</SwipeableListItem> </SwipeableListItem>
) )