Use the shadcn components we already have (charts, search fields, budget toggle) - #162
Merged
Conversation
Two charts passed Recharts' bare `<Tooltip>`, which paints a hardcoded
white box with dark text — unreadable in dark mode. The net worth chart
had already hand-rolled a `CustomTooltip` to get around exactly that, and
CLAUDE.md has claimed "Recharts v3 (via shadcn Chart)" all along, so the
missing piece was the component itself.
All four charts now render inside `ChartContainer` with `ChartTooltip` /
`ChartLegendContent`, and series labels come from `ChartConfig` rather
than per-series `name` props, which keeps legend and tooltip in step.
Two local adaptations to the generated `ui/chart.tsx`:
- The shadcn CLI emits `import { cn } from "cn"` and tries to install a
package by that name. Repointed at `@/lib/utils`.
- Added a `valueFormatter` prop. Every value this app charts is an integer
cent count, and upstream renders values with `toLocaleString()` — 123456
where $1,234.56 belongs. Recharts' own `formatter` replaces the whole
tooltip row, dropping the colour indicator and the series name with it.
The net worth chart keeps a five-line wrapper for the one thing the shared
content does not do: dropping the null half of the split coverage series,
which would otherwise print the same date twice.
Both search inputs positioned their magnifier by hand — a `relative` wrapper, an `absolute` icon and matching `pl-8` on the input — while `ui/input-group.tsx` sat in the repo with no callers at all. InputGroup also focuses the input when the icon is clicked, which the old `pointer-events-none` icon could not do.
The Category/Flex control was two raw buttons in a bordered flex row, re-deriving selected styling that ToggleGroup already carries — and the same segmented control is built from ToggleGroup in four other places. Base UI hands back an empty array when the active item is clicked again, so the handler ignores anything that is not a budget type, keeping one option always selected. The selected item takes the `aria-pressed:bg-primary` override the appearance toggle documents: the shared `bg-muted` sits too close to the page ground to read as selected on a control whose only job is showing which option is active.
This was referenced Sep 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An audit of where the codebase hand-rolls UI that shadcn/ui already provides. The library turns out to be used well overall — 24 of 27 installed components have consumers, there is exactly one raw
<input>and no raw<select>— so this PR takes only the three clearest gaps.1.
chartwas never installed, and it was costing us a dark-mode bugspending-chart.tsxpassed Recharts' bare<Tooltip>in both donut and bar mode. That renders a hardcoded white box with dark text, which is unreadable against a dark background.net-worth-area-chart.tsxhad already hand-rolled aCustomTooltipto route around the same problem, and CLAUDE.md has described the stack as "Recharts v3 (via shadcn Chart)" the whole time — the component itself was just missing.All four charts now render inside
ChartContainerwithChartTooltip/ChartLegendContent, and series labels come fromChartConfigrather than per-seriesnameprops, so legend and tooltip can't drift apart.Two deliberate local edits to the generated
ui/chart.tsx, both commented in the file:import { cn } from "cn"and tries to add an npm package by that name. Repointed at@/lib/utils.valueFormatterprop. Every value this app charts is an integer cent count, and upstream renders values withtoLocaleString()—123,456where$1,234.56belongs. Recharts' ownformattercan't be used for this: it replaces the entire tooltip row, taking the colour indicator and series name with it.Category-keyed charts keep inline colours on purpose. Category names are user data (
Groceries & Dining), and those can't be emitted as--color-<key>custom properties.The net worth chart keeps a five-line wrapper for the one thing
ChartTooltipContentdoesn't do: dropping the null half of the split coverage series, which would otherwise print the same date twice.2.
input-groupwas installed with zero consumersbill-search.tsxandtransaction-filters.tsxboth positioned their magnifier by hand — arelativewrapper, anabsoluteicon, matchingpl-8on the input. NowInputGroup/InputGroupAddon/InputGroupInput. Small a11y gain: the addon focuses the input when clicked, which the oldpointer-events-noneicon could not.3. A segmented control built from raw buttons
budget-page-header.tsxbuilt its Category/Flex switch from two<button>s in a bordered flex row, re-deriving selected-state styling. The same control is built fromToggleGroupin four other places (appearance-toggle,date-range-selector,account-list,holdings-table).It takes the
aria-pressed:bg-primaryoverride thatappearance-toggledocuments — the sharedbg-mutedsits too close to the page ground to read as selected on a control whose only job is showing which option is active.Testing
pnpm typecheckandpnpm lintpass.Charts have no automated coverage in this repo (vitest is
environment: "node"and matches*.test.tsonly), so I verified them with a throwaway jsdom harness — all four mount without throwing, legend labels resolve fromChartConfig, and the tooltip renders$1,234.56rather than123,456. The harness is not included; adding a jsdom project to vitest so component tests are possible at all is a separate call.Deliberately not in this PR
Two further findings from the audit, both of which shift pixels and want a mock first:
<button>s insidePopoverContent(transaction-filters×4,report-filter-bar×2,date-range-popover).DropdownMenuwould give roving arrow-key focus, typeahead androle="menuitem"— today those lists are Tab-only. This is the largest remaining a11y win.<table>(bill-list,portfolio-reconciliation,widgets/account-balances) whileui/table.tsxhas six consumers. Needs care around cell padding and thetable-fixedclipping fix from fix(dashboard): stop Account Balances and Investments from clipping their own numbers #159.Not recommended: the ~20 hand-rolled
rounded-lg bordershells are visually different from this repo'sCard(rounded-xl bg-card ring-1 ring-foreground/10, no border). Converting them is a redesign, not a refactor.🤖 Generated with Claude Code