ModalChrome
NAME
App::Moneymoor::View::ModalChrome - the one canonical set of
Selkie::Styles every modal, dialog and settings pane paints its
chrome with.
DESCRIPTION
Modals are built fresh on every open, and each one used to hand-roll its own bold-blue title, grey label and dim italic hint from raw hex literals. Six copies of the same three values meant two things: the modals were frozen on a pseudo-TokyoNight palette no matter which of the eleven themes the user picked, and "make hints a touch dimmer" was a six-file change that would inevitably miss one.
modal-styles replaces those literals with one theme-derived bundle.
Every entry is a semantic role, not a colour: pick the role that
describes what the text is and the palette decides what it looks
like.
ROLES
titleโ the modal's own heading. Paletteaccent, bold: the one place in a modal that gets the theme's signature hue.labelโ a field caption ("Project", "Duration"). Reads at fullfg-baseweight because the user has to scan these to find the field they want.dimโ supporting body copy that is present but not shouting: the grammar / syntax explainer walls, read-only context lines.fg-dim.hintโ the trailing key-hint line ("Enter save ยท Esc cancel").fg-dimplus italic โ the italic keeps it behindlabelin the hierarchy without dropping tofg-dimmer, which in several palettes (Nord's canonical comment grey most of all) falls under 1.5:1 contrast againstbg-baseand renders the hint effectively invisible.hintanddimtext never sit adjacent in practice, so sharing the hue costs nothing.warnโ a consequence the user should read before acting (the login screen's "there is no passphrase reset").fg-amber, italic.errorโ a failed action or invalid input.fg-red.
EXAMPLES
The whole bundle at once, destructured into the names the modal's build method already used:
use App::Moneymoor::View::ModalChrome;
method build(App::Moneymoor::Theme :$theme! --> Selkie::Widget::Modal) {
my %styles = modal-styles(:$theme);
$content.add: Selkie::Widget::Text.new(
text => ' Edit task', sizing => Sizing.fixed(1),
style => %styles<title>,
);
$content.add: Selkie::Widget::Text.new(
text => ' Title', sizing => Sizing.fixed(1),
style => %styles<label>,
);
$content.add: Selkie::Widget::Text.new(
text => ' Enter save ยท Esc cancel', sizing => Sizing.fixed(1),
style => %styles<hint>,
);
}
The returned styles are plain Selkie::Style objects โ immutable
value types โ so a modal can safely hand the same one to a dozen
Text widgets, and a caller that wants a variation merges rather
than mutates:
my %styles = modal-styles(:$theme);
my $bold-label = %styles<label>.merge(Selkie::Style.new(bold => True));
THEME SWATCH
swatch-spans(:$theme) is a second, unrelated export sharing this
module because it is the same kind of thing: a pure, theme-derived
piece of chrome consumed by Screen::Settings' live theme-preview
row. It returns eight Selkie::Widget::RichText::Spans โ one โโ
colour chip per named palette slot, in a fixed order:
use App::Moneymoor::View::ModalChrome;
my @chips = swatch-spans(theme => $theme);
# accent, fg-red, fg-amber, fg-green, fg-blue, fg-purple, fg-cyan, bg-surface
Callers interleave their own separator spans (a plain-space Span
between chips, as the doc example on swatch-spans above shows) โ
the eight returned spans carry only the colour, so a positional test
never has to skip over spacing.
NOTES
Modals are rebuilt per-open (see App::Moneymoor::Screen::Main::Modals),
so injecting the theme at construction is enough to re-theme them: a
live theme swap while a modal is open isn't reachable, because the
Settings screen that performs the swap is itself the modal-free path.
No live-patch machinery is needed here, unlike the long-lived widgets
App::Moneymoor::Screen::Main.rebuild-captured-styles has to fix up.
SEE ALSO
App::Moneymoor::Theme โ the palette these roles resolve against.