{% load brickwork_icons i18n %} {% comment %} Trend indicator (VIZ-017): a standalone directional caption, extracted from _stat.html's former inline trend block (VIZ-002, BR-BW-TPL-007) so a table cell or a scorecard can render the same accessible trend contract without pulling in the whole KPI tile. Structural, consumed via {% include %} (no render-time enforcement needed, matching _stat.html's own consumption mode: this component carries the same trend/trend_label contract with zero Python-side validation, so introducing a tag here would add behaviour the extraction is not supposed to change). Since icvoss/django-brickwork#334, _stat.html itself consumes this partial rather than keeping a second copy: there is one implementation of the trend contract, this one, not two. Class name: bw-trend, not the block's former bw-stat__trend. That was a BEM element scoped to .bw-stat, which is meaningless once this markup renders inside a table cell or a scorecard with no .bw-stat ancestor: keeping the scoped name here, unqualified, would not match any selector a consumer or this package's own CSS reasonably targets outside a .bw-stat. bw-trend is this partial's own root class as a standalone consumer sees it. css_class (optional, matching bw_icon's/bw_chart_mount's own seam): extra class tokens appended after bw-trend and the bw-trend-- modifier, space-joined, escaped. _stat.html (#334) uses this to add its own bw-stat__trend back onto the element it includes this partial for: bw-stat__trend was documented public surface before the extraction (docs/DESIGN.md, docs/BRANDING.md name it in prose about the X-fg token, and it carried its own styling) so #334 retains it alongside bw-trend on the SAME element rather than retiring it; a consumer selecting .bw-stat__trend directly still matches. This is one implementation (this partial) rendered with two class names when _stat.html is the caller, not two implementations: a table cell or scorecard that includes this partial directly gets only bw-trend, because they never had bw-stat__trend to begin with. bw-trend rather than bw-trend-indicator because this package uses the SHORTEST name that identifies the component, which is not the same rule as "prefer bare nouns". Both forms ship: bare where one word names the thing (bw-badge, bw-card, bw-spinner) and compound where it takes two (bw-data-table, bw-empty-state, bw-chart-mount). "table" alone would be ambiguous against every other table on a page and "state" alone means nothing, so those earn their second word. "trend" earns nothing from "indicator": every component here indicates something, so the suffix distinguishes this from no other component. The package does not pad names, and a future reader who greps for bw-data-table should find that rule rather than a bare-noun preference the compounds would falsify. Trend accessibility (BR-BW-TPL-007, AC-BW-073/074, unchanged from _stat.html): whenever ``trend`` is set, the template renders a directional glyph (arrow-up / arrow-down / minus, decorative) PLUS a visually hidden text fallback ("increased" / "decreased" / "unchanged"), so the direction never rides on colour alone by construction, even when the caller supplies no ``trend_label``. ``trend_label``, when given, refines the accessible text (e.g. "12% up on last month") and renders visibly beside the glyph. Colour (icvoss/django-brickwork#388, replacing the pre-3.15 behaviour): the bw-trend-- modifier now carries NO colour of its own; all three direction states (up/down/flat) render in the same neutral ink (--bw-color-fg-muted). Direction is a fact about which way a number moved, never whether that is good news: hard-coding up=success-green and down=danger-red painted judgement onto direction and produced rows where a falling cost (trend="down", correctly announcing "decreased" to a screen reader) rendered danger-red while its own trend_label said "which is better" beside it. Sighted and assistive-tech readers got opposite meanings from the same element, which is exactly the failure the direction/judgement split exists to prevent. sentiment ("good" | "bad", optional): the ONLY way to get judgement colour now, set deliberately by the caller, independent of trend. Renders an additional bw-trend--good/bw-trend--bad modifier (and, when css_class is given, a matching --good/--bad) alongside the direction modifier, never instead of it: a caller with a falling cost that is good news passes trend="down" sentiment="good", so the accessible text still says "decreased" and the colour separately says "this is the good outcome". A falsy value (the default) renders no sentiment modifier and no judgement colour, matching this template's existing falsy-renders-nothing convention for trend_label/css_class. Any other, truthy but unrecognised value renders no sentiment modifier either (same closed-set, silent no-match-no-render posture as _stat_comparison.html's own size argument; this is an include-only template with no Python-side tag validation, so an unrecognised value is a caller error the template declines to guess at rather than raises). Matched against the closed set with explicit {% if %}/{% elif %} literals, never interpolated as {{ sentiment }}, for the same reason _stat_comparison.html's own size argument gives: this file has no validation layer, so a bare {{ sentiment }} in a class attribute would be a live attribute-injection seam for any caller passing an unexpected value. Required context: trend ("up" | "down" | "flat"): the direction of change. A falsy value (None, "", 0, False, or the key absent) renders nothing, matching _stat.html's own ``{% if trend %}`` guard: no data means no trend row, not a spurious "unchanged". Any other, truthy but unrecognised value takes the flat treatment, matching _stat.html's own fallback. Optional: trend_label (str): the visible delta text beside the glyph. sentiment ("good" | "bad"): opt-in judgement colour, independent of trend (see the header note above). Omitted (the default) renders the direction modifier only, in neutral ink. css_class (str): extra class tokens appended after bw-trend, the bw-trend-- modifier, and (when sentiment is set) the bw-trend-- modifier (see the header note above). Escaped; never Brickwork's own trusted markup. Usage (a table cell, VIZ-017's own motivating case):: {% include "brickwork/components/_trend_indicator.html" with trend="up" trend_label="12% on last month" %} Usage with opt-in judgement colour (a falling cost that is good news):: {% include "brickwork/components/_trend_indicator.html" with trend="down" trend_label="2.6 days lower, which is better" sentiment="good" %} States: up/down/flat, each its own .bw-trend-- DIRECTION class, neutral in colour; the glyph and hidden text are identical in shape across up and down (only the flat state renders a different, non-directional glyph (minus, not an arrow) and never shows a visible word for the state itself: the visible slot is always the caller-supplied trend_label, matching _stat.html's own treatment). good/bad, when sentiment is set, are a SEPARATE, additive colour state layered on top of the direction state, never replacing it. Accessibility: a trend is never colour-only: a directional glyph (decorative) always pairs with visually-hidden text naming the direction in words ("increased"/"decreased"/"unchanged"), regardless of whether the caller supplies a visible trend_label. Judgement, when the caller states one via sentiment, is reinforcement of what trend_label's words already say, never the sole signal of it. Responsive: no breakpoint switch; no width-dependent CSS on any .bw-trend* selector. {% endcomment %} {% if trend %} {% if trend == "up" %}{% bw_icon "arrow-up" size="sm" decorative=True %}{% elif trend == "down" %}{% bw_icon "arrow-down" size="sm" decorative=True %}{% else %}{% bw_icon "minus" size="sm" decorative=True %}{% endif %} {% if trend == "up" %}{% translate "increased" %}{% elif trend == "down" %}{% translate "decreased" %}{% else %}{% translate "unchanged" %}{% endif %} {% if trend_label %}{{ trend_label }}{% endif %} {% endif %}