mirror of
https://github.com/we-promise/sure.git
synced 2026-06-08 20:29:05 +00:00
* feat(ds): elevate dropdown overlays and stabilize selection check gutter Menus and popovers floated at the same elevation as inline cards (shadow-border-xs), so dropdowns blended into the content beneath them. Bump DS::Menu and DS::Popover panels to shadow-border-lg. DS::MenuItem rendered its leading icon only when present, so a selection check shifted the row's text out of alignment with the unselected rows. Add a `selected:` param that reserves a fixed-width check gutter (check when selected, empty otherwise) so row text stays aligned. Apply the same reserved gutter to the bespoke category dropdown row, and add a `selectable` menu preview. * feat(dashboard): unify per-widget period selectors into one picker The dashboard rendered three identical period <select>s (cashflow, outflows, net worth), each writing the same global User#default_period and full-reloading the page via turbo_frame "_top" — so changing one changed all. Replace them with a single shared UI::PeriodPicker (DS::Menu of period links) in a toolbar, and wrap the sections grid in a "dashboard_sections" Turbo frame so a period change swaps only the dashboard (no full-page reload). Reuse the same picker on the account chart, removing its duplicate select. * refactor(dashboard): use DS::MenuItem selected: gutter in period picker Now that DS::MenuItem reserves a check gutter, the period picker passes selected: instead of a conditional leading check icon, so the current period's row stays aligned with the rest. * fix(ds): expose menu selection via menuitemradio + aria-checked Selectable DS::MenuItem rows conveyed selection only visually. Render them as role="menuitemradio" with aria-checked so assistive tech gets the selection state of single-select lists, merging the menu ARIA contract with any caller-supplied aria. Addresses CodeRabbit review feedback. * refactor(dashboard): drop redundant aria-current from period picker DS::MenuItem now exposes selection via menuitemradio + aria-checked, so the period picker no longer needs its own aria-current. Update the component test to assert the new ARIA. * fix(ds): include selectable roles in menu roving-focus query DS::MenuItem selectable rows render as role=menuitemradio, but the menu controller built its roving-focus list from [role=menuitem] only, leaving single-select menus with no keyboard focus/arrow handling. Query the menuitemradio/menuitemcheckbox roles too. Addresses Codex review feedback. * fix(dashboard): keep non-picker links out of frame + fix custom-month picker - turbo_frame_tag "dashboard_sections" now targets _top so ordinary links inside the sections (e.g. Balance Sheet account links) navigate the page instead of failing to resolve inside the frame. - Period.current_month_for / last_month_for carry their semantic key for custom-month families, so the picker shows the right label and checks the right option instead of falling back to 30D. Addresses Codex review feedback. * fix(dashboard): announce selected period in picker trigger's accessible name The static "Select time period" aria-label overrode the visible selected label as the trigger's accessible name, so assistive tech kept announcing the same name regardless of selection. Interpolate the selected short label into the aria-label and pin it with a component test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
47 lines
1.4 KiB
Ruby
47 lines
1.4 KiB
Ruby
class UI::PeriodPicker < ApplicationComponent
|
|
# Unified time-range selector shared by the dashboard and account charts.
|
|
#
|
|
# Renders a DS::Menu as a flat list of link items — one per Period. Each item
|
|
# is a GET link to `url` carrying `?period=<key>` (plus any `extra_params`),
|
|
# which re-renders `frame` (a Turbo Frame id) with the chosen period. When
|
|
# `frame` is nil the links fall back to a normal Turbo Drive visit.
|
|
#
|
|
# The selected period is marked with a check icon and `aria-current`, and the
|
|
# trigger button shows its label.
|
|
#
|
|
# NOTE: `url` must be a path without a query string; pass query state via
|
|
# `extra_params` so the picker can compose `?period=…` cleanly.
|
|
attr_reader :selected_key, :url, :frame, :extra_params, :placement
|
|
|
|
def initialize(selected:, url:, frame: nil, extra_params: {}, placement: "bottom-end")
|
|
@selected_key = selected.respond_to?(:key) ? selected.key : selected.to_s
|
|
@url = url
|
|
@frame = frame
|
|
@extra_params = (extra_params || {}).symbolize_keys
|
|
@placement = placement
|
|
end
|
|
|
|
def periods
|
|
Period.all
|
|
end
|
|
|
|
def selected_label
|
|
period_for(selected_key).label_short
|
|
end
|
|
|
|
def selected?(key)
|
|
key == selected_key
|
|
end
|
|
|
|
def href_for(key)
|
|
"#{url}?#{extra_params.merge(period: key).to_query}"
|
|
end
|
|
|
|
private
|
|
def period_for(key)
|
|
Period.from_key(key)
|
|
rescue Period::InvalidKeyError
|
|
Period.last_30_days
|
|
end
|
|
end
|