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> </Box>
{/* ── Row 2: pinned filters quick access ──────────────────────────────── */} {/* ── 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 <CustomFilterChips
filters={savedFilters} filters={savedFilters}
activeFilterId={activeFilterId} activeFilterId={activeFilterId}
@@ -612,8 +616,8 @@ const ChoreToolbar = ({
/> />
)} )}
{/* ── Row 3: active filter chips ──────────────────────────────────────── */} {/* ── Row 3: active (temp/ad-hoc) filter chips ──────────────────────────── */}
{hasAnyActive && ( {tempConditionCount > 0 && (
<ActiveFilterChips <ActiveFilterChips
chips={inlineChips} chips={inlineChips}
onOpen={openFilterSheet} onOpen={openFilterSheet}

View File

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