reduce the noise by making the color just a small badge

This commit is contained in:
Mo Tarbin
2026-07-27 23:43:39 -04:00
parent 5a4f67d70a
commit b145bb422b
2 changed files with 138 additions and 128 deletions

View File

@@ -601,7 +601,11 @@ const ChoreToolbar = ({
</Box>
{/* ── Row 2: pinned filters quick access ──────────────────────────────── */}
{!hasAnyActive && (
{/* Stays visible even while a saved filter is active (it self-highlights)
so switching between pinned filters never requires clearing first.
Only steps aside for an ad-hoc/temp condition set, which has no
matching pinned identity to show. */}
{tempConditionCount === 0 && (
<CustomFilterChips
filters={savedFilters}
activeFilterId={activeFilterId}
@@ -612,8 +616,8 @@ const ChoreToolbar = ({
/>
)}
{/* ── Row 3: active filter chips ──────────────────────────────────────── */}
{hasAnyActive && (
{/* ── Row 3: active (temp/ad-hoc) filter chips ──────────────────────────── */}
{tempConditionCount > 0 && (
<ActiveFilterChips
chips={inlineChips}
onOpen={openFilterSheet}

View File

@@ -2,6 +2,7 @@ import {
Check,
Delete,
Edit,
MoreVert,
Settings,
Star,
StarBorder,
@@ -66,158 +67,163 @@ const CustomFilterChips = ({
handleMenuClose()
}
const sortedFilters = [...filters].sort((a, b) => {
if (a.isPinned && !b.isPinned) return -1
if (!a.isPinned && b.isPinned) return 1
return (b.usageCount || 0) - (a.usageCount || 0)
})
if (sortedFilters.filter(f => f.isPinned).length === 0) return null
const pinnedFilters = filters
.filter(f => f.isPinned)
.sort((a, b) => (b.usageCount || 0) - (a.usageCount || 0))
if (pinnedFilters.length === 0) return null
return (
<Box
sx={{
display: 'flex',
gap: 1,
overflowX: 'auto',
py: 1,
'&::-webkit-scrollbar': {
display: 'none',
},
scrollbarWidth: 'none', // Firefox
msOverflowStyle: 'none', // IE and Edge
}}
>
{sortedFilters.map(filter => {
const isActive = activeFilterId === filter.id
const hasWarning = !filter.isValid
const hasCustomColor = !!filter.color && !hasWarning
const textColor = hasCustomColor
? getTextColorFromBackgroundColor(filter.color)
: undefined
<Box sx={{ position: 'relative' }}>
<Box
sx={{
display: 'flex',
gap: 0.75,
overflowX: 'auto',
py: 1,
pr: 2,
'&::-webkit-scrollbar': {
display: 'none',
},
scrollbarWidth: 'none', // Firefox
msOverflowStyle: 'none', // IE and Edge
}}
>
{pinnedFilters.map(filter => {
const isActive = activeFilterId === filter.id
const hasWarning = !filter.isValid
const badgeColor = filter.color || 'var(--joy-palette-neutral-400)'
const badgeTextColor = filter.color
? getTextColorFromBackgroundColor(filter.color)
: '#ffffff'
const displayCount =
filter.count > 99 ? '99+' : (filter.count ?? 0)
return (
<Tooltip
key={filter.id}
title={
hasWarning
? `Filter has issues: ${filter.validationIssues?.join(', ')}`
: `${filter.description ? filter.description + ' - ' : ''}${filter.count} tasks${filter.overdueCount > 0 ? ` (${filter.overdueCount} overdue)` : ''}`
}
placement='bottom'
>
<div onClick={() => !hasWarning && onFilterClick(filter.id)}>
return (
<Tooltip
key={filter.id}
title={
hasWarning
? `Filter has issues: ${filter.validationIssues?.join(', ')}`
: `${filter.description ? filter.description + ' - ' : ''}${filter.count} tasks${filter.overdueCount > 0 ? ` (${filter.overdueCount} overdue)` : ''}`
}
placement='bottom'
>
<Chip
variant='solid'
variant={isActive ? 'solid' : 'outlined'}
color={hasWarning ? 'warning' : isActive ? 'primary' : 'neutral'}
size='md'
onContextMenu={e => handleContextMenu(e, filter)}
onClick={() => !hasWarning && onFilterClick(filter.id)}
sx={{
cursor: hasWarning ? 'not-allowed' : 'pointer',
transition: 'all 0.2s ease',
px: 1.0,
py: 0.5,
transition: 'background-color 0.15s ease, color 0.15s ease',
px: 1,
height: 32,
display: 'flex',
alignItems: 'center',
opacity: hasWarning ? 0.7 : isActive ? 1 : 0.85,
...(hasCustomColor && {
backgroundColor: `${filter.color} !important`,
color: `${textColor} !important`,
'&:hover': {
backgroundColor: `${filter.color} !important`,
filter: 'brightness(0.95)',
opacity: 1,
},
}),
flexShrink: 0,
fontWeight: isActive ? 600 : 500,
'&:hover': {
backgroundColor: isActive ? undefined : 'neutral.softHoverBg',
},
}}
startDecorator={
<Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center' }}>
{isActive ? (
<Check
sx={{
fontSize: '1rem',
color: hasCustomColor ? textColor : 'primary.500',
}}
/>
) : (
<Chip
size='sm'
variant='solid'
sx={{
...(hasCustomColor
? {
bgcolor:
textColor === '#FFFFFF'
? '#00000040'
: '#FFFFFF40',
color: textColor,
border: `1px solid ${textColor}30`,
}
: {}),
}}
color={
hasCustomColor
? undefined
: hasWarning
? 'warning'
: 'neutral'
}
>
{filter.count}
</Chip>
)}
</Box>
!hasWarning && (
<Box
sx={{
width: 20,
height: 20,
borderRadius: '50%',
bgcolor: badgeColor,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
}}
>
{isActive ? (
<Check
sx={{ fontSize: '0.85rem', color: badgeTextColor }}
/>
) : (
<Typography
level='body-xs'
sx={{
fontSize: '0.65rem',
lineHeight: 1,
color: badgeTextColor,
}}
>
{displayCount}
</Typography>
)}
</Box>
)
}
endDecorator={
<IconButton
size='sm'
variant='plain'
color='neutral'
onClick={e => {
e.stopPropagation()
handleContextMenu(e, filter)
}}
sx={{
'--IconButton-size': '20px',
ml: 0.25,
opacity: 0.6,
'&:hover': { opacity: 1, backgroundColor: 'transparent' },
}}
>
<MoreVert sx={{ fontSize: '0.95rem' }} />
</IconButton>
}
// endDecorator={
// <Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center' }}>
// {hasWarning && <Warning sx={{ fontSize: '1rem' }} />}
// {!hasWarning && filter.overdueCount > 0 && (
// <Chip size='sm' color='danger' variant='solid'>
// {filter.overdueCount}
// </Chip>
// )}
// </Box>
// }
>
<Typography
level='body-sm'
fontWeight={isActive ? 'md' : 'normal'}
fontWeight='inherit'
sx={{
whiteSpace: 'nowrap',
maxWidth: 100,
overflow: 'hidden',
textOverflow: 'ellipsis',
display: 'flex',
alignItems: 'center',
height: '100%',
lineHeight: 1,
...(hasCustomColor && {
color: textColor,
}),
color: 'inherit',
}}
>
{filter.name}
</Typography>
</Chip>
</div>
</Tooltip>
)
})}
</Tooltip>
)
})}
<IconButton
variant='outlined'
size='sm'
<IconButton
variant='outlined'
color='neutral'
size='sm'
sx={{ borderRadius: 24, flexShrink: 0 }}
onClick={e => {
e.preventDefault()
e.stopPropagation()
navigate('/filters')
}}
>
<Settings sx={{ fontSize: '1.1rem' }} />
</IconButton>
</Box>
{/* Fade hint that more chips are scrollable off the trailing edge */}
<Box
sx={{
borderRadius: 24,
pointerEvents: 'none',
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
width: 24,
background:
'linear-gradient(to right, transparent, var(--joy-palette-background-surface, #fff))',
}}
onClick={e => {
e.preventDefault()
e.stopPropagation()
navigate('/filters')
}}
>
<Settings />
</IconButton>
/>
<Menu
anchorEl={menuAnchor}