doxa-maps/library/composables/useCardSurface.js
Doxa Maps 458ca5ff59 The prayer-progress cards become shared components too
The three goal cards, the stat row and their bars were the regions
dashboard's own markup. They are library components now, and the plugin
renders them instead of holding a copy:

  GoalCard      title + figure + a goal line pinned to the card's bottom,
                with a slot for whatever sits between
  StatCard      one figure and its caption
  ProgressBar   a single-value track, or a segmented one with a legend
  useCardSurface  the card surface both cards share, injected once per
                root under a stable label instead of copied into each

The bar and its legend now read from ONE list of segments, so a width and
the number printed beside it come from the same figure and cannot drift
apart; before, each was computed separately. A segment's colour is a prop
carrying the same token its .rd-seg--full/--partial/--none class used to
set, so a shared bar needs no vocabulary about prayer and those three
rules are deleted rather than left unreachable.

ProgressBar carries its own reduced-motion guard. The plugin's reset is
scoped to .rd-root and would not have followed the component anywhere
else, which would have reintroduced the one-second sliding bar their own
site suppresses.

The rendered DOM is unchanged for all four instances, measured by
server-rendering the real components and comparing against the markup
that was deleted, including that the bar widths equal the percentOfSubject()
they replace.

check:regions-uses-library grows to cover all four, asserting both halves
again: the plugin imports and renders them, and no longer ships their
markup or CSS. The .rd-root strong rule STAYS in the plugin -- UupgsList
renders a <strong> in the same shadow root and depends on it, which a
file-scoped read would have called dead.
2026-09-27 04:14:31 +00:00

44 lines
2.7 KiB
JavaScript

/**
* useCardSurface.js — the DX-life card surface, as ONE rule set shared by every card component.
*
* WHY THIS IS A MODULE AND NOT A COPY IN EACH CARD. Vue only injects the ROOT component's <style>
* into a shadow root, so every library component that renders inside one carries its own CSS and
* injects it with useShadowStyles (see that file). StatCard and GoalCard are two components that
* need the SAME surface -- radius, elevation, padding, and the two variants -- and a copy in each is
* a rule set that will drift the first time one of them is tuned.
*
* THE STABLE LABEL IS THE WHOLE TRICK. useShadowStyles dedups on its label, so both cards (and any
* consumer that renders .rd-card markup of its own) can call useCardSurface() and exactly ONE
* <style data-shadow-styles="dxlife-card-surface"> lands per shadow root, no matter how many card
* components mount inside it. Calling it from N components is not N stylesheets.
*
* ★ THE CLASS NAMES KEEP THEIR `rd-` PREFIX, DELIBERATELY. It came from the regions-dashboard port
* and means nothing outside it. Renaming would change no pixel and would cost the traceability from
* each declaration back to the DX-life rule and the measurement that fixed it, which is the only
* reason these values can be defended. The names are internal to the components; a consumer writes
* <StatCard> and never types them. A rename is a safe later cleanup, not a prerequisite for reuse.
*
* CONTRACT: the shadow root must already carry the DX-life token layer (--dx-radius, --dx-elevated,
* --dx-text, --dx-dark, --dx-on-dark, --spacing-*). The regions plugin injects it as DXLIFE_TOKENS.
* Without it these rules resolve to nothing -- they are a skin over that scale, not a replacement.
*/
import { useShadowStyles } from './useShadowStyles.js'
export const CARD_SURFACE_CSS =
// ── .card: blocks/_cards.scss:9-21. radius --border-radius-xlg (35px);
// padding clamp(--spacing-lg, 6vw, --spacing-2xl) clamp(--spacing-lg, 6vw, --spacing-3xl).
'.rd-card{ position:relative; border-radius:var(--dx-radius);' +
' background:var(--dx-elevated); color:var(--dx-text);' +
' padding:clamp(var(--spacing-lg), 6cqw, var(--spacing-2xl)) clamp(var(--spacing-lg), 6cqw, var(--spacing-3xl)); }' +
// .card[padding-small] -> --spacing-lg flat
'.rd-card--small{ padding:var(--spacing-lg); }' +
// .card[data-variant="secondary"] -> surface-brand-dark on text-on-brand-light
'.rd-card--secondary{ background:var(--dx-dark); color:var(--dx-on-dark); }'
/**
* Injects the card surface into the containing shadow root, once, under a stable label.
* Call it at setup() time from any component that renders .rd-card markup.
*/
export function useCardSurface() {
useShadowStyles(CARD_SURFACE_CSS, 'dxlife-card-surface')
}