Skip to content

Improve category picker UX: grouped searchable icons + color picker#3

Merged
sergdort merged 2 commits intomainfrom
feat/categories-v2-icon-picker
Feb 26, 2026
Merged

Improve category picker UX: grouped searchable icons + color picker#3
sergdort merged 2 commits intomainfrom
feat/categories-v2-icon-picker

Conversation

@LogenNineFingersIsAlive
Copy link
Collaborator

Summary

Upgrades category creation/editing UX with a much richer icon and color selection flow.

What changed

  • expanded category icon catalog into grouped v2 set (bills/finance, transport, food, subscriptions, health, work, entertainment, family/pets, travel)
  • switched icon selection to grouped/searchable autocomplete with keyword matching
  • added curated color swatch picker for category color
  • added live category preview (icon + color + name)
  • improved mobile icon picker usability by disabling keyboard input in icon field and keeping list scroll-focused

Files

  • apps/pwa/src/features/categories/constants.ts
  • apps/pwa/src/features/categories/CategoriesScreen.tsx
  • apps/pwa/src/features/categories/dialogs/AddCategoryDialog.tsx
  • apps/pwa/src/features/categories/dialogs/EditCategoryDialog.tsx
  • apps/pwa/src/features/categories/types.ts
  • apps/pwa/src/features/categories/utils.ts
  • apps/pwa/src/api.ts

Validation

  • pre-commit lint-staged checks passed
  • pre-push lint checks passed
  • pnpm --filter @tithe/pwa build passes

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the category creation and editing UX by replacing simple dropdown icon selection with a grouped, searchable autocomplete system and adding a color picker with curated swatches. The changes improve the mobile experience by disabling keyboard input in the icon field and include a live preview showing the icon, color, and name together.

Changes:

  • Expanded category icon catalog with 50+ icons organized into 9 semantic groups (bills/finance, transport, food, subscriptions, health, work, entertainment, family/pets, travel)
  • Replaced dropdown icon selector with searchable autocomplete supporting keyword matching and grouping
  • Added curated color picker with 16 color swatches for category customization

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
apps/pwa/src/features/categories/types.ts Added color field to CategoryEditDraft interface
apps/pwa/src/features/categories/utils.ts Updated buildDraftFromCategory to include color field
apps/pwa/src/features/categories/constants.ts Expanded icon catalog to 50+ icons with CategoryIconOption type including label, group, and keywords; added 16-color palette
apps/pwa/src/features/categories/dialogs/AddCategoryDialog.tsx Replaced dropdown with Autocomplete for icons, added color picker with swatches, added live category preview
apps/pwa/src/features/categories/dialogs/EditCategoryDialog.tsx Replaced dropdown with Autocomplete for icons, added color picker with swatches, added live category preview
apps/pwa/src/features/categories/CategoriesScreen.tsx Updated draft creation and patch object to include color field
apps/pwa/src/api.ts Added optional color field to category update API body

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 181 to 194
<Box
key={option}
component="button"
type="button"
onClick={() => setColor(option)}
sx={{
width: 28,
height: 28,
borderRadius: '50%',
border: color === option ? '2px solid #111827' : '1px solid #CFD8DC',
backgroundColor: option,
cursor: 'pointer',
}}
/>
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The color swatch buttons should have keyboard navigation support. Consider adding tabIndex={0} and onKeyDown handlers to allow keyboard users to select colors using Enter or Space keys.

Copilot uses AI. Check for mistakes.
Comment on lines 186 to 193
sx={{
width: 28,
height: 28,
borderRadius: '50%',
border: color === option ? '2px solid #111827' : '1px solid #CFD8DC',
backgroundColor: option,
cursor: 'pointer',
}}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The border color '#111827' and '#CFD8DC' are hardcoded. Consider using theme colors like 'theme.palette.grey[900]' and 'theme.palette.divider' for better maintainability and theme consistency.

Suggested change
sx={{
width: 28,
height: 28,
borderRadius: '50%',
border: color === option ? '2px solid #111827' : '1px solid #CFD8DC',
backgroundColor: option,
cursor: 'pointer',
}}
sx={(theme) => ({
width: 28,
height: 28,
borderRadius: '50%',
border:
color === option
? `2px solid ${theme.palette.grey[900]}`
: `1px solid ${theme.palette.divider}`,
backgroundColor: option,
cursor: 'pointer',
})}

Copilot uses AI. Check for mistakes.
Comment on lines 181 to 194
<Box
key={option}
component="button"
type="button"
onClick={() => setColor(option)}
sx={{
width: 28,
height: 28,
borderRadius: '50%',
border: color === option ? '2px solid #111827' : '1px solid #CFD8DC',
backgroundColor: option,
cursor: 'pointer',
}}
/>
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The color swatch buttons lack accessible labels. Consider adding aria-label attributes with the color name or value to make the color picker accessible to screen reader users.

Copilot uses AI. Check for mistakes.
Comment on lines 114 to 127
<Box
key={option}
component="button"
type="button"
onClick={() => onChangeDraft({ color: option })}
sx={{
width: 28,
height: 28,
borderRadius: '50%',
border: draft.color === option ? '2px solid #111827' : '1px solid #CFD8DC',
backgroundColor: option,
cursor: 'pointer',
}}
/>
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The color swatch buttons should have keyboard navigation support. Consider adding tabIndex={0} and onKeyDown handlers to allow keyboard users to select colors using Enter or Space keys.

Copilot uses AI. Check for mistakes.
Comment on lines 209 to 216
{(() => {
const IconComponent = CATEGORY_ICON_COMPONENTS[icon] ?? CategoryIcon;
return (
<Avatar sx={{ width: 32, height: 32, bgcolor: `${color}22`, color }}>
<IconComponent fontSize="small" />
</Avatar>
);
})()}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IIFE (Immediately Invoked Function Expression) pattern is unnecessary here. Consider extracting the IconComponent directly without wrapping it in an IIFE for better readability and performance.

Copilot uses AI. Check for mistakes.
Comment on lines 197 to 201
{ name: 'shopping_bag', label: 'Shopping bag', group: 'subscriptions', keywords: ['shopping'] },
{
name: 'shopping_cart',
label: 'Shopping cart',
group: 'subscriptions',
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shopping bag and shopping cart icons are grouped under "subscriptions" but these typically represent one-time shopping/groceries rather than recurring subscriptions. Consider moving these to a "shopping" group or keeping them in "bills/finance".

Suggested change
{ name: 'shopping_bag', label: 'Shopping bag', group: 'subscriptions', keywords: ['shopping'] },
{
name: 'shopping_cart',
label: 'Shopping cart',
group: 'subscriptions',
{ name: 'shopping_bag', label: 'Shopping bag', group: 'bills/finance', keywords: ['shopping'] },
{
name: 'shopping_cart',
label: 'Shopping cart',
group: 'bills/finance',

Copilot uses AI. Check for mistakes.
width: 28,
height: 28,
borderRadius: '50%',
border: draft.color === option ? '2px solid #111827' : '1px solid #CFD8DC',
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The border color '#111827' is hardcoded. Consider using theme colors like 'theme.palette.grey[900]' or 'theme.palette.text.primary' for better maintainability and theme consistency.

Suggested change
border: draft.color === option ? '2px solid #111827' : '1px solid #CFD8DC',
border:
draft.color === option
? `2px solid ${theme.palette.text.primary}`
: '1px solid #CFD8DC',

Copilot uses AI. Check for mistakes.
Comment on lines 114 to 127
<Box
key={option}
component="button"
type="button"
onClick={() => onChangeDraft({ color: option })}
sx={{
width: 28,
height: 28,
borderRadius: '50%',
border: draft.color === option ? '2px solid #111827' : '1px solid #CFD8DC',
backgroundColor: option,
cursor: 'pointer',
}}
/>
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The color swatch buttons lack accessible labels. Consider adding aria-label attributes with the color name or value to make the color picker accessible to screen reader users.

Copilot uses AI. Check for mistakes.
'none',
);
const [icon, setIcon] = useState<string>('savings');
const [color, setColor] = useState<string>('#2E7D32');
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default color '#2E7D32' is hardcoded in multiple places. Consider extracting it as a constant (e.g., 'DEFAULT_CATEGORY_COLOR') in constants.ts and importing it to avoid duplication and improve maintainability.

Copilot uses AI. Check for mistakes.
{(() => {
const IconComponent = CATEGORY_ICON_COMPONENTS[icon] ?? CategoryIcon;
return (
<Avatar sx={{ width: 32, height: 32, bgcolor: `${color}22`, color }}>
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alpha transparency value '22' (hex) appended to the color results in very low opacity (~13%). This may not provide enough visual distinction for the background. Consider using a higher value like '33' (20% opacity) or '44' (27% opacity) for better visibility.

Suggested change
<Avatar sx={{ width: 32, height: 32, bgcolor: `${color}22`, color }}>
<Avatar sx={{ width: 32, height: 32, bgcolor: `${color}33`, color }}>

Copilot uses AI. Check for mistakes.
- extract DEFAULT_CATEGORY_COLOR constant (no more hardcoded duplication)
- use theme palette for swatch borders instead of hardcoded hex
- add aria-label on color swatch buttons for accessibility
- remove unnecessary 'as CategoryIconOption[]' type casts
- remove IIFE pattern from preview icon rendering
- bump avatar background alpha from 22 to 33 (13% → 20% opacity)
- move shopping_bag/shopping_cart from subscriptions to new shopping group
@sergdort sergdort merged commit d97bc0d into main Feb 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants