@boost33/ui 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +27 -1
  2. package/dist/components/AccountMenu/AccountMenu.d.ts +131 -0
  3. package/dist/components/AccountMenu/icons.d.ts +11 -0
  4. package/dist/components/AccountMenu/labels.d.ts +39 -0
  5. package/dist/components/AppShell/AppShell.d.ts +71 -18
  6. package/dist/components/AppShell/AppShell.js +33 -35
  7. package/dist/components/AppShell/AppShell.js.map +1 -1
  8. package/dist/components/AppShell/AppShell.module.css.js +11 -11
  9. package/dist/components/AppSwitcher/AppSwitcher.d.ts +23 -1
  10. package/dist/components/AppSwitcher/AppSwitcher.js +49 -43
  11. package/dist/components/AppSwitcher/AppSwitcher.js.map +1 -1
  12. package/dist/components/AppSwitcher/AppSwitcher.module.css.js +36 -36
  13. package/dist/components/AppSwitcher/labels.js.map +1 -1
  14. package/dist/components/CommandPalette/CommandPalette.d.ts +127 -0
  15. package/dist/components/CommandPalette/icons.d.ts +8 -0
  16. package/dist/components/CommandPalette/labels.d.ts +94 -0
  17. package/dist/components/DotMatrixLoader/DotMatrixLoader.d.ts +21 -0
  18. package/dist/components/DotMatrixLoader/DotMatrixLoader.js +18 -0
  19. package/dist/components/DotMatrixLoader/DotMatrixLoader.js.map +1 -0
  20. package/dist/components/DotMatrixLoader/DotMatrixLoader.module.css.js +12 -0
  21. package/dist/components/DotMatrixLoader/DotMatrixLoader.module.css.js.map +1 -0
  22. package/dist/components/LaunchLoader/LaunchLoader.d.ts +50 -0
  23. package/dist/components/LaunchLoader/LaunchLoader.js +70 -0
  24. package/dist/components/LaunchLoader/LaunchLoader.js.map +1 -0
  25. package/dist/components/LaunchLoader/LaunchLoader.module.css.js +26 -0
  26. package/dist/components/LaunchLoader/LaunchLoader.module.css.js.map +1 -0
  27. package/dist/components/NotificationCenter/NotificationCenter.d.ts +162 -0
  28. package/dist/components/NotificationCenter/icons.d.ts +34 -0
  29. package/dist/components/NotificationCenter/labels.d.ts +112 -0
  30. package/dist/components/ProductMark/ProductMark.d.ts +54 -1
  31. package/dist/components/ProductMark/ProductMark.js +23 -19
  32. package/dist/components/ProductMark/ProductMark.js.map +1 -1
  33. package/dist/components/Sidebar/Sidebar.d.ts +17 -3
  34. package/dist/components/Sidebar/Sidebar.js +5 -5
  35. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  36. package/dist/components/WalletChip/WalletChip.d.ts +150 -27
  37. package/dist/components/WalletChip/WalletChip.js +38 -35
  38. package/dist/components/WalletChip/WalletChip.js.map +1 -1
  39. package/dist/components/WalletChip/WalletChip.module.css.js +36 -36
  40. package/dist/components/WalletChip/labels.d.ts +65 -47
  41. package/dist/components/WalletChip/labels.js +15 -20
  42. package/dist/components/WalletChip/labels.js.map +1 -1
  43. package/dist/fill-template.d.ts +10 -0
  44. package/dist/fill-template.js +10 -0
  45. package/dist/fill-template.js.map +1 -0
  46. package/dist/index.d.ts +10 -4
  47. package/dist/index.js +38 -39
  48. package/dist/index.js.map +1 -1
  49. package/dist/launch-gradient.d.ts +112 -0
  50. package/dist/launch-gradient.js +60 -0
  51. package/dist/launch-gradient.js.map +1 -0
  52. package/dist/nav-height-budget.d.ts +4 -0
  53. package/dist/nav-height-budget.js +18 -19
  54. package/dist/nav-height-budget.js.map +1 -1
  55. package/dist/style.css +1 -1
  56. package/dist/theme.css +40 -14
  57. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"WalletChip.js","sources":["../../../src/components/WalletChip/WalletChip.tsx"],"sourcesContent":["'use client'\n\n// A client module: the chip wires `onTopUp` to a button's onClick, which a\n// Server Component cannot carry. The rest of the shell stays on the server --\n// see Sidebar and B33-846.\n\nimport styles from './WalletChip.module.css'\nimport { DEFAULT_WALLET_LOCALE, formatTokens, thousandsFigure } from './format'\nimport { RetryMark, WalletMark } from './icons'\nimport { resolveWalletChipLabels, type WalletChipLabels } from './labels'\n\n// Past this fraction of the cycle's grant burned, the chip stops resting. The\n// spec's threshold, in one place so the derivation and the styling agree.\nconst THRESHOLD_BURN = 0.8\n\nexport interface WalletChipProps {\n /** Remaining granted tokens. Granted burns first and expires. */\n granted: number\n /** Remaining purchased tokens. Purchased never expires. */\n purchased: number\n /**\n * The grant issued for this cycle. The denominator for \"how much has burned\",\n * and so for the threshold; drawn as the bar's full width alongside purchased\n * so the filled portion shortens as the month runs.\n */\n grantCycle: number\n /** When the current grant expires, preformatted for display, e.g. \"30 Sep\". */\n grantExpires?: string\n /**\n * The threshold state's runway line, preformatted, e.g. \"About four days left\n * at your current burn.\" Passed in rather than derived: the burn estimate is\n * the product's to make, not the chip's -- the chip renders what it is given.\n */\n burnNote?: string\n /** The balance could not be confirmed. Renders as unknown, never as zero. */\n unknown?: boolean\n /** The 56px rail. Abbreviates the balance and turns the split on its side. */\n collapsed?: boolean\n /** Top up. Wiring for the one button the sidebar is allowed to have. */\n onTopUp?: () => void\n /**\n * The chip's user-facing strings. Every field has an English default, so a\n * consumer supplies only the ones it translates; the first two consumers are\n * French-default. The package takes no i18n dependency -- it renders what it\n * is given (B33-849).\n *\n * Every field can be a plain string, so the whole object crosses the React\n * Server Component boundary and a Next App Router page can build it. The four\n * labels that carry a value mark its position with a `{placeholder}`; each\n * also accepts a function, which only works from a Client Component (B33-880).\n */\n labels?: Partial<WalletChipLabels>\n /**\n * The BCP-47 language tag the chip's figures are formatted in. Defaults to\n * `en-US`, which is what the chip rendered before this prop existed.\n *\n * `labels` gives the consumer the chip's words; this gives it their numbers.\n * French writes `4 600` where English writes `4,600`, and a label cannot fix\n * it -- the label functions receive the figures already formatted, so before\n * this prop there was no hook at all (B33-876). Grouping and the decimal\n * separator are CLDR facts about a locale rather than a property of the\n * consumer's sentence, so a tag is enough: `Intl.NumberFormat` knows the rest,\n * and a tag is a plain string, which a Server Component can pass where a\n * formatter function could not.\n *\n * It reaches the visible figures and, because the same strings are\n * interpolated into them, the `bar` and `railBalance` accessible names.\n *\n * The `k` after the rail's abbreviated balance is a word, not a number, so it\n * is `labels.railFigure` and not this.\n */\n locale?: string\n}\n\n/**\n * The granted/purchased bar. Granted is drawn first, because granted burns\n * first. The accessible label is passed in whole rather than assembled here:\n * word order is the consumer's, not English's (B33-849).\n */\nfunction Bar({ granted, purchased, cycleTotal, tone, label }: { granted: number; purchased: number; cycleTotal: number; tone: 'rest' | 'threshold'; label: string }) {\n const width = (n: number) => `${(n / cycleTotal) * 100}%`\n return (\n <div className={styles.bar} data-tone={tone} role=\"img\" aria-label={label}>\n <span className={styles.segmentGranted} data-testid=\"segment-granted\" style={{ width: width(granted) }} />\n <span className={styles.segmentPurchased} data-testid=\"segment-purchased\" style={{ width: width(purchased) }} />\n </div>\n )\n}\n\nexport function WalletChip({ granted, purchased, grantCycle, grantExpires, burnNote, unknown = false, collapsed = false, onTopUp, labels, locale = DEFAULT_WALLET_LOCALE }: WalletChipProps) {\n const t = resolveWalletChipLabels(labels)\n // Assembled lazily: the unknown state draws no bar, and a consumer's `bar`\n // implementation can throw on the degraded path's 0,0,0 figures. Calling it\n // only where a bar renders keeps that crash surface off the render that exists\n // precisely because fetching the balance already failed (B33-875).\n const buildBarLabel = () =>\n t.bar(formatTokens(granted, locale), formatTokens(purchased, locale), formatTokens(granted + purchased, locale))\n const balance = granted + purchased\n // Everything the cycle started with: the grant plus the purchased balance\n // sitting alongside it. Measuring against this is what makes the bar shorten\n // as tokens burn instead of only re-proportioning to a full width.\n const cycleTotal = grantCycle + purchased\n const burned = grantCycle > 0 ? (grantCycle - granted) / grantCycle : 0\n const isThreshold = !unknown && burned >= THRESHOLD_BURN\n\n if (collapsed) {\n // The abbreviated balance, assembled once: it is both the rail's visible\n // text and the figure the group's accessible name is built around, and a\n // sighted user and a screen-reader user must not be told different numbers.\n // Built only when a balance is known, for the same reason buildBarLabel is\n // lazy -- the unknown rail shows no figure, and a consumer's railFigure\n // should not run on a degraded path it never renders on (B33-875).\n const railBalance = unknown ? undefined : t.railFigure(thousandsFigure(balance, locale))\n return (\n <div className={styles.rail} role=\"group\" aria-label={railBalance === undefined ? t.railBalanceUnknown : t.railBalance(railBalance)} tabIndex={0}>\n <WalletMark className={styles.railIcon} />\n <span className={styles.railFigure}>{railBalance ?? '—'}</span>\n {unknown ? <span className={styles.srOnly}>{t.railUnknown}</span> : <Bar granted={granted} purchased={purchased} cycleTotal={cycleTotal} tone={isThreshold ? 'threshold' : 'rest'} label={buildBarLabel()} />}\n </div>\n )\n }\n\n return (\n <div className={styles.chip} role=\"group\" aria-label={t.region} tabIndex={0} data-threshold={isThreshold ? 'true' : undefined} data-unknown={unknown ? 'true' : undefined}>\n <div className={styles.header}>\n <span className={styles.name}>{t.name}</span>\n {isThreshold && <span className={styles.pill}>{Math.round(burned * 100)}%</span>}\n </div>\n\n {unknown ? (\n <div className={styles.figure}>\n <span className={styles.amount} aria-hidden=\"true\">\n {'—'}\n </span>\n <RetryMark className={styles.retry} />\n <span className={styles.srOnly}>{t.balanceUnknown}</span>\n </div>\n ) : (\n <div className={styles.figure}>\n <span className={styles.amount}>{formatTokens(balance, locale)}</span>\n <span className={styles.unit}>{t.unit}</span>\n </div>\n )}\n\n {unknown ? (\n <div className={styles.bar} data-tone=\"unknown\" aria-hidden=\"true\" />\n ) : (\n <Bar granted={granted} purchased={purchased} cycleTotal={cycleTotal} tone={isThreshold ? 'threshold' : 'rest'} label={buildBarLabel()} />\n )}\n\n {!unknown && !isThreshold && (\n <div className={styles.details}>\n <dl className={styles.split}>\n <div className={styles.splitRow}>\n <dt className={styles.splitLabel}>\n <span className={styles.dotGranted} aria-hidden=\"true\" />\n {t.granted}\n </dt>\n <dd className={styles.splitAmount}>{formatTokens(granted, locale)}</dd>\n </div>\n <div className={styles.splitRow}>\n <dt className={styles.splitLabel}>\n <span className={styles.dotPurchased} aria-hidden=\"true\" />\n {t.purchased}\n </dt>\n <dd className={styles.splitAmount}>{formatTokens(purchased, locale)}</dd>\n </div>\n </dl>\n {grantExpires && <p className={styles.footer}>{t.grantExpiry(grantExpires)}</p>}\n </div>\n )}\n\n {isThreshold && burnNote && <p className={styles.burnNote}>{burnNote}</p>}\n\n {!unknown && (\n <button type=\"button\" className={styles.topUp} data-grown={isThreshold ? 'true' : undefined} onClick={onTopUp}>\n {t.topUp}\n </button>\n )}\n </div>\n )\n}\n"],"names":["THRESHOLD_BURN","Bar","granted","purchased","cycleTotal","tone","label","width","n","jsxs","styles","jsx","WalletChip","grantCycle","grantExpires","burnNote","unknown","collapsed","onTopUp","labels","locale","DEFAULT_WALLET_LOCALE","t","resolveWalletChipLabels","buildBarLabel","formatTokens","balance","burned","isThreshold","railBalance","thousandsFigure","WalletMark","RetryMark"],"mappings":"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"}
1
+ {"version":3,"file":"WalletChip.js","sources":["../../../src/components/WalletChip/WalletChip.tsx"],"sourcesContent":["'use client'\n\n// A client module: the chip wires `onTopUp` to a button's onClick, which a\n// Server Component cannot carry. The rest of the shell stays on the server --\n// see Sidebar and B33-846.\n\nimport styles from './WalletChip.module.css'\nimport { DEFAULT_WALLET_LOCALE, formatTokens, thousandsFigure } from './format'\nimport { RetryMark, WalletMark } from './icons'\nimport { resolveWalletChipLabels, type WalletChipLabels } from './labels'\n\n// Past this fraction of the cycle's grant burned, the chip stops resting. The\n// spec's threshold, in one place so the derivation and the styling agree.\n//\n// It stays in the package and is not a prop, which is the whole reason\n// `burnedFraction` is a fraction rather than a `threshold?: boolean` (B33-483).\n// The consumer measures its own burn; 80% is where four products agree that\n// measurement means something, and a boolean would hand each of them the policy\n// and let it drift. It also happens to agree with the middle rung of Score's own\n// burndown alerts, so adopting it changed no product decision.\nconst THRESHOLD_BURN = 0.8\n\n// The floor under a non-zero bar segment, named once so the width expression\n// below reads as what it is. The value and its derivation live in the token --\n// see --spacing-wallet-segment-min in tokens.css.\nconst SEGMENT_MIN = 'var(--spacing-wallet-segment-min)'\n\n/**\n * A bar segment's CSS `width`: its honest share of everything the cycle started\n * with, floored so that a non-zero amount is never drawn below one visible mark.\n *\n * `max()` rather than a `min-width` on the segment classes, and the reason is the\n * zero case (B33-590). A stylesheet sees only the percentage, and no CSS length\n * can tell 0% from 0.3% -- a bare `min-width` would floor a wallet that holds\n * none of that kind of token into a phantom sliver, which is the same lie in the\n * other direction. The decision needs the amount itself, so it is made here where\n * the amount is, in one expression instead of a rule plus a signal from the\n * component to switch it off. `max()` also keeps the honest percentage wherever\n * the share clears the floor: the floor only ever wins when the truth is\n * unpaintable.\n *\n * Why it needs flooring at all: once a seller has bought tokens the grant is a\n * small share of the cycle, and a small share of a 240px bar is a fraction of a\n * pixel. Score measured 3,000 granted rendering at `width: 0.3%` -- 0.72px, which\n * paints nothing, while the ledger still held the grant. Both segments are\n * floored: purchased burns second, so it drains to zero through every small\n * number on the way, and 10 purchased on a spent 3,000 grant is 0.33% of the\n * cycle and the seller's entire remaining balance.\n *\n * This is the second time the rendering has been reported. Score fixed it in its\n * own wallet markup; B33-482 replaced that markup with this component, which\n * carried no floor. Its record of the first fix is still on disk there, as dead\n * code, at components/app-shell/wallet-bar.ts.\n */\nfunction segmentWidth(amount: number, cycleTotal: number): string {\n // A cycle of nothing has no shares in it. Guarded because the divisor is\n // reachable -- a known balance of zero on a cycle that granted zero rendered\n // `width: NaN%`, which a browser drops rather than reports.\n const share = cycleTotal > 0 ? (amount / cycleTotal) * 100 : 0\n if (amount <= 0) return `${share}%`\n return `max(${share}%, ${SEGMENT_MIN})`\n}\n\n/**\n * The props that say nothing about a balance, so both states take them\n * identically: where the chip is drawn, what it is wired to, and the words and\n * number formats it renders with.\n */\ninterface WalletChipChromeProps {\n /** The 56px rail. Abbreviates the balance and turns the split on its side. */\n collapsed?: boolean\n /**\n * Top up. Wiring for the one button the sidebar is allowed to have.\n *\n * **It is also what makes that button exist.** Omit it and the chip draws no\n * top-up affordance at all, in every state, and that is the fix B33-1270 made.\n * The render used to be gated on `!unknown` alone, so a consumer that passed\n * no handler got a visible control that did nothing and could not be removed\n * -- `.topUp`'s class name is CSS-Modules-hashed, so there is no selector to\n * reach it from outside the package.\n *\n * That was not hypothetical in either consumer. The Wallet passes no handler\n * and ships the dead button today\n * (`boostledger/src/app/(wallet)/WalletShell.tsx`), and Score wrote a whole\n * client module to arm it across a Server Component boundary rather than live\n * with it (`boostscore/components/app-shell/wallet-chip-top-up.tsx`, which\n * says so at length).\n *\n * Nothing renders in the button's place. It carries no information of its own\n * -- only its own affordance -- so a consumer with nowhere to send someone is\n * better served by the chip without it than by a disabled one they did not\n * ask for (ADR-0010, \"Not entitled is not disabled\", on why unavailable is not\n * the same as disabled). B33-1047 is the other half: an `href` form of the\n * same affordance, for a consumer that cannot pass a callback at all.\n */\n onTopUp?: () => void\n /**\n * The chip's user-facing strings. Every field has an English default, so a\n * consumer supplies only the ones it translates; the first two consumers are\n * French-default. The package takes no i18n dependency -- it renders what it\n * is given (B33-849).\n *\n * Every field is a plain string, so the whole object crosses the React Server\n * Component boundary and a Next App Router page can build it. The four labels\n * that carry a value mark its position with a `%placeholder%` (B33-1277). None\n * of them takes a function: that form only ever worked from a Client Component\n * and nothing in the type said so (B33-880, B33-1278).\n */\n labels?: Partial<WalletChipLabels>\n /**\n * The BCP-47 language tag the chip's figures are formatted in. Defaults to\n * `en-US`, which is what the chip rendered before this prop existed.\n *\n * `labels` gives the consumer the chip's words; this gives it their numbers.\n * French writes `4 600` where English writes `4,600`, and a label cannot fix\n * it -- the label functions receive the figures already formatted, so before\n * this prop there was no hook at all (B33-876). Grouping and the decimal\n * separator are CLDR facts about a locale rather than a property of the\n * consumer's sentence, so a tag is enough: `Intl.NumberFormat` knows the rest,\n * and a tag is a plain string, which a Server Component can pass where a\n * formatter function could not.\n *\n * It reaches the visible figures and, because the same strings are\n * interpolated into them, the `bar` and `railBalance` accessible names.\n *\n * The `k` after the rail's abbreviated balance is a word, not a number, so it\n * is `labels.railFigure` and not this.\n */\n locale?: string\n}\n\n/**\n * The chip with a balance the consumer stands behind. The two remaining figures\n * are required, because this is the state that renders them.\n */\nexport interface WalletChipKnownProps extends WalletChipChromeProps {\n /**\n * Absent or `false`. The discriminant: it is what makes the figures below\n * required here and forbidden in {@link WalletChipUnknownProps}.\n */\n unknown?: false\n /** Remaining granted tokens. Granted burns first and expires. */\n granted: number\n /** Remaining purchased tokens. Purchased never expires. */\n purchased: number\n /**\n * What this cycle's grant started with, if the consumer knows. The bar's full\n * width alongside purchased, so the part already burned draws as empty track\n * and the filled portion shortens as the month runs.\n *\n * **Optional, and that is the fix B33-483 made.** It used to be required, and\n * no consumer could satisfy it honestly: it is the Ledger's\n * `grant_tokens_per_cycle`, and it cannot be reconstructed from a balance --\n * `cycleUsedPct` counts purchased-bucket spend, is not `(cycle total -\n * remaining)` once a credit, refund or void has touched the account, and is not\n * bounded at 100. So the Wallet passed `grantCycle = granted`, which pins\n * `burned` at 0 and held the threshold state unreachable in the reference\n * implementation, and Score passed a local per-plan belief instead.\n *\n * Omitted, the bar measures against `granted + purchased` -- the balance\n * itself. It then draws full and claims nothing about what burned, which is the\n * honest rendering when nobody can say.\n *\n * It no longer decides the threshold on its own: {@link burnedFraction} does\n * that, and this is only consulted when that is absent.\n */\n grantCycle?: number\n /**\n * How much of the cycle's grant has burned, as a fraction -- `0.88` for 88%.\n * Drives the threshold state, and is the figure the chip rounds into its pill.\n *\n * Passed in rather than derived, for exactly the reason {@link burnNote}\n * already is: the burn is the product's measurement to make, not the chip's.\n * The chip kept the arithmetic while taking the runway line as a prop, and fed\n * it from a denominator no product could supply -- so the measurement was the\n * chip's in name and nobody's in fact (B33-483).\n *\n * The *policy* stays here. `THRESHOLD_BURN` is 0.8 in this package, so four\n * products trip at the same place and cannot drift to four different numbers.\n * That is why this is a fraction and not a `threshold?: boolean`: a boolean\n * would move the 80% decision into each consumer, and it would leave the pill\n * with no figure to print.\n *\n * Supplied, it wins. Absent, the chip derives it from {@link grantCycle} as it\n * always did; absent with no `grantCycle` either, nothing is claimed and the\n * chip rests.\n *\n * It is not clamped. A consumer that passes a percentage where a fraction\n * belongs renders a visibly absurd pill rather than a plausible \"100%\", and a\n * wrong figure that looks wrong is the one that gets fixed.\n */\n burnedFraction?: number\n /** When the current grant expires, preformatted for display, e.g. \"30 Sep\". */\n grantExpires?: string\n /**\n * The threshold state's runway line, preformatted, e.g. \"About four days left\n * at your current burn.\" Passed in rather than derived: the burn estimate is\n * the product's to make, not the chip's -- the chip renders what it is given.\n */\n burnNote?: string\n}\n\n/**\n * The chip whose balance could not be confirmed. It renders an em dash, no bar\n * and no button, so **it takes no figures at all** -- each one below is\n * `?: never`.\n *\n * That is deliberate and it is the second half of B33-483. The figures were\n * required in every state, so both consumers manufactured zeroes to get past the\n * type and then apologised for them in a comment -- \"these zeroes are never\n * rendered ... but they are not a balance and must not be read as one\"\n * (`boostscore/components/app-shell/wallet-chip.tsx`), and the same again in\n * `wallet-slot.tsx` and `boostledger/src/app/(wallet)/layout.tsx`. A zero is a\n * confident claim about someone's money. The type now refuses it rather than\n * asking a comment to.\n *\n * `undefined` is still accepted, so a consumer that must build one props object\n * can write `{ unknown: true, granted: undefined }` instead of branching.\n */\nexport interface WalletChipUnknownProps extends WalletChipChromeProps {\n /** The balance could not be confirmed. Renders as unknown, never as zero. */\n unknown: true\n granted?: never\n purchased?: never\n grantCycle?: never\n burnedFraction?: never\n /** Not rendered in this state: the footer only draws on a resting chip. */\n grantExpires?: never\n /** Not rendered in this state: the runway line only draws past the threshold. */\n burnNote?: never\n}\n\n/**\n * A discriminated union on `unknown`, and the discriminant has consequences for\n * how a consumer *types* its own code, not only what it passes:\n *\n * - A helper that returns props for both states needs an explicit\n * `: WalletChipProps` return annotation. Without one, TypeScript widens the\n * `unknown: true` in an object literal to `boolean`, which matches neither\n * member, and the spread at the call site stops compiling.\n * - `Omit<WalletChipProps, 'onTopUp'>` is not what it looks like. `Omit` over a\n * union flattens it into one object and loses the discrimination. Omit from\n * the members instead -- `Omit<WalletChipKnownProps, 'onTopUp'> |\n * Omit<WalletChipUnknownProps, 'onTopUp'>` -- which is why both are exported.\n */\nexport type WalletChipProps = WalletChipKnownProps | WalletChipUnknownProps\n\n/**\n * The granted/purchased bar. Granted is drawn first, because granted burns\n * first. The accessible label is passed in whole rather than assembled here:\n * word order is the consumer's, not English's (B33-849).\n */\nfunction Bar({ granted, purchased, cycleTotal, tone, label }: { granted: number; purchased: number; cycleTotal: number; tone: 'rest' | 'threshold'; label: string }) {\n return (\n <div className={styles.bar} data-tone={tone} role=\"img\" aria-label={label}>\n <span className={styles.segmentGranted} data-testid=\"segment-granted\" style={{ width: segmentWidth(granted, cycleTotal) }} />\n <span className={styles.segmentPurchased} data-testid=\"segment-purchased\" style={{ width: segmentWidth(purchased, cycleTotal) }} />\n </div>\n )\n}\n\nexport function WalletChip({ granted = 0, purchased = 0, grantCycle, burnedFraction, grantExpires, burnNote, unknown = false, collapsed = false, onTopUp, labels, locale = DEFAULT_WALLET_LOCALE }: WalletChipProps) {\n const t = resolveWalletChipLabels(labels)\n // Assembled lazily: the unknown state draws no bar, and a consumer's `bar`\n // implementation can throw on the zeroes the destructure above defaults the\n // degraded path's absent figures to. Calling it only where a bar renders keeps\n // that crash surface off the render that exists precisely because fetching the\n // balance already failed (B33-875). B33-483 stopped the consumer having to pass\n // those zeroes; the chip still has to pick something, so the laziness stays.\n const buildBarLabel = () =>\n t.bar(formatTokens(granted, locale), formatTokens(purchased, locale), formatTokens(granted + purchased, locale))\n const balance = granted + purchased\n // Everything the cycle started with: the grant plus the purchased balance\n // sitting alongside it. Measuring against this is what makes the bar shorten\n // as tokens burn instead of only re-proportioning to a full width.\n //\n // With no `grantCycle` the denominator falls back to `granted + purchased`, so\n // the bar fills and there is no burned track -- the honest drawing when nobody\n // can say what the cycle started with (B33-483). The zero-division guard inside\n // segmentWidth still covers the case where that balance is itself zero.\n const cycleTotal = (grantCycle ?? granted) + purchased\n // How much of the cycle's grant has burned. The consumer's own figure wins;\n // `grantCycle` derives it exactly as it always did; with neither, nothing is\n // claimed and the chip rests. `grantCycle` is tested against undefined rather\n // than leaned on for falsiness, because 0 and \"not supplied\" both resolve to a\n // burn of 0 and must keep doing so.\n const derivedBurn = grantCycle !== undefined && grantCycle > 0 ? (grantCycle - granted) / grantCycle : 0\n const burned = burnedFraction ?? derivedBurn\n const isThreshold = !unknown && burned >= THRESHOLD_BURN\n\n if (collapsed) {\n // The abbreviated balance, assembled once: it is both the rail's visible\n // text and the figure the group's accessible name is built around, and a\n // sighted user and a screen-reader user must not be told different numbers.\n // Built only when a balance is known, for the same reason buildBarLabel is\n // lazy -- the unknown rail shows no figure, and a consumer's railFigure\n // should not run on a degraded path it never renders on (B33-875).\n const railBalance = unknown ? undefined : t.railFigure(thousandsFigure(balance, locale))\n return (\n <div className={styles.rail} role=\"group\" aria-label={railBalance === undefined ? t.railBalanceUnknown : t.railBalance(railBalance)} tabIndex={0}>\n <WalletMark className={styles.railIcon} />\n <span className={styles.railFigure}>{railBalance ?? '—'}</span>\n {unknown ? <span className={styles.srOnly}>{t.railUnknown}</span> : <Bar granted={granted} purchased={purchased} cycleTotal={cycleTotal} tone={isThreshold ? 'threshold' : 'rest'} label={buildBarLabel()} />}\n </div>\n )\n }\n\n return (\n <div className={styles.chip} role=\"group\" aria-label={t.region} tabIndex={0} data-threshold={isThreshold ? 'true' : undefined} data-unknown={unknown ? 'true' : undefined}>\n <div className={styles.header}>\n <span className={styles.name}>{t.name}</span>\n {isThreshold && <span className={styles.pill}>{Math.round(burned * 100)}%</span>}\n </div>\n\n {unknown ? (\n <div className={styles.figure}>\n <span className={styles.amount} aria-hidden=\"true\">\n {'—'}\n </span>\n <RetryMark className={styles.retry} />\n <span className={styles.srOnly}>{t.balanceUnknown}</span>\n </div>\n ) : (\n <div className={styles.figure}>\n <span className={styles.amount}>{formatTokens(balance, locale)}</span>\n <span className={styles.unit}>{t.unit}</span>\n </div>\n )}\n\n {unknown ? (\n <div className={styles.bar} data-tone=\"unknown\" aria-hidden=\"true\" />\n ) : (\n <Bar granted={granted} purchased={purchased} cycleTotal={cycleTotal} tone={isThreshold ? 'threshold' : 'rest'} label={buildBarLabel()} />\n )}\n\n {!unknown && !isThreshold && (\n <div className={styles.details}>\n <dl className={styles.split}>\n <div className={styles.splitRow}>\n <dt className={styles.splitLabel}>\n <span className={styles.dotGranted} aria-hidden=\"true\" />\n {t.granted}\n </dt>\n <dd className={styles.splitAmount}>{formatTokens(granted, locale)}</dd>\n </div>\n <div className={styles.splitRow}>\n <dt className={styles.splitLabel}>\n <span className={styles.dotPurchased} aria-hidden=\"true\" />\n {t.purchased}\n </dt>\n <dd className={styles.splitAmount}>{formatTokens(purchased, locale)}</dd>\n </div>\n </dl>\n {grantExpires && <p className={styles.footer}>{t.grantExpiry(grantExpires)}</p>}\n </div>\n )}\n\n {isThreshold && burnNote && <p className={styles.burnNote}>{burnNote}</p>}\n\n {/* A handler is what makes the button exist, not only what it does when\n pressed (B33-1270). `!unknown` alone drew it for a consumer with\n nowhere to send anyone, and a hashed class name left them no way to\n take it back out -- see `onTopUp`. Tested against `undefined` rather\n than for truthiness so that `onTopUp={undefined}`, which is what a\n consumer's own absent value looks like, reads the same as omitting it. */}\n {!unknown && onTopUp !== undefined && (\n <button type=\"button\" className={styles.topUp} data-grown={isThreshold ? 'true' : undefined} onClick={onTopUp}>\n {t.topUp}\n </button>\n )}\n </div>\n )\n}\n"],"names":["THRESHOLD_BURN","SEGMENT_MIN","segmentWidth","amount","cycleTotal","share","Bar","granted","purchased","tone","label","jsxs","styles","jsx","WalletChip","grantCycle","burnedFraction","grantExpires","burnNote","unknown","collapsed","onTopUp","labels","locale","DEFAULT_WALLET_LOCALE","t","resolveWalletChipLabels","buildBarLabel","formatTokens","balance","derivedBurn","burned","isThreshold","railBalance","thousandsFigure","WalletMark","RetryMark"],"mappings":"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"}
@@ -1,56 +1,56 @@
1
- const t = "_chip_1th8y_37", _ = "_header_1th8y_74", n = "_name_1th8y_83", s = "_pill_1th8y_162", e = "_figure_1th8y_171", o = "_amount_1th8y_186", r = "_unit_1th8y_195", a = "_retry_1th8y_200", i = "_bar_1th8y_209", c = "_segmentGranted_1th8y_230", l = "_segmentPurchased_1th8y_231", h = "_details_1th8y_251", y = "_split_1th8y_260", d = "_splitRow_1th8y_267", p = "_splitLabel_1th8y_273", u = "_splitAmount_1th8y_281", m = "_dotGranted_1th8y_289", g = "_dotPurchased_1th8y_290", b = "_footer_1th8y_304", f = "_burnNote_1th8y_312", G = "_topUp_1th8y_87", P = "_rail_1th8y_378", w = "_railIcon_1th8y_392", A = "_railFigure_1th8y_398", F = "_srOnly_1th8y_411", I = {
1
+ const t = "_chip_xxga2_37", _ = "_header_xxga2_74", a = "_name_xxga2_83", n = "_pill_xxga2_162", s = "_figure_xxga2_171", e = "_amount_xxga2_186", o = "_unit_xxga2_195", x = "_retry_xxga2_200", r = "_bar_xxga2_209", i = "_segmentGranted_xxga2_230", c = "_segmentPurchased_xxga2_231", l = "_details_xxga2_275", g = "_split_xxga2_284", d = "_splitRow_xxga2_291", p = "_splitLabel_xxga2_297", u = "_splitAmount_xxga2_305", m = "_dotGranted_xxga2_313", h = "_dotPurchased_xxga2_314", b = "_footer_xxga2_328", f = "_burnNote_xxga2_336", y = "_topUp_xxga2_87", G = "_rail_xxga2_402", P = "_railIcon_xxga2_416", w = "_railFigure_xxga2_422", A = "_srOnly_xxga2_435", F = {
2
2
  chip: t,
3
3
  header: _,
4
- name: n,
5
- pill: s,
6
- figure: e,
7
- amount: o,
8
- unit: r,
9
- retry: a,
10
- bar: i,
11
- segmentGranted: c,
12
- segmentPurchased: l,
13
- details: h,
14
- split: y,
4
+ name: a,
5
+ pill: n,
6
+ figure: s,
7
+ amount: e,
8
+ unit: o,
9
+ retry: x,
10
+ bar: r,
11
+ segmentGranted: i,
12
+ segmentPurchased: c,
13
+ details: l,
14
+ split: g,
15
15
  splitRow: d,
16
16
  splitLabel: p,
17
17
  splitAmount: u,
18
18
  dotGranted: m,
19
- dotPurchased: g,
19
+ dotPurchased: h,
20
20
  footer: b,
21
21
  burnNote: f,
22
- topUp: G,
23
- rail: P,
24
- railIcon: w,
25
- railFigure: A,
26
- srOnly: F
22
+ topUp: y,
23
+ rail: G,
24
+ railIcon: P,
25
+ railFigure: w,
26
+ srOnly: A
27
27
  };
28
28
  export {
29
- o as amount,
30
- i as bar,
29
+ e as amount,
30
+ r as bar,
31
31
  f as burnNote,
32
32
  t as chip,
33
- I as default,
34
- h as details,
33
+ F as default,
34
+ l as details,
35
35
  m as dotGranted,
36
- g as dotPurchased,
37
- e as figure,
36
+ h as dotPurchased,
37
+ s as figure,
38
38
  b as footer,
39
39
  _ as header,
40
- n as name,
41
- s as pill,
42
- P as rail,
43
- A as railFigure,
44
- w as railIcon,
45
- a as retry,
46
- c as segmentGranted,
47
- l as segmentPurchased,
48
- y as split,
40
+ a as name,
41
+ n as pill,
42
+ G as rail,
43
+ w as railFigure,
44
+ P as railIcon,
45
+ x as retry,
46
+ i as segmentGranted,
47
+ c as segmentPurchased,
48
+ g as split,
49
49
  u as splitAmount,
50
50
  p as splitLabel,
51
51
  d as splitRow,
52
- F as srOnly,
53
- G as topUp,
54
- r as unit
52
+ A as srOnly,
53
+ y as topUp,
54
+ o as unit
55
55
  };
56
56
  //# sourceMappingURL=WalletChip.module.css.js.map
@@ -1,34 +1,48 @@
1
1
  /**
2
2
  * Every user-facing string the chip renders. Supplied whole -- the strings that
3
- * carry a value take a `{placeholder}` template (or a function) rather than
4
- * fragments, because word order is not a property of English and cannot be
5
- * reassembled on the consumer's side.
3
+ * carry a value take a `%placeholder%` template rather than fragments, because
4
+ * word order is not a property of English and cannot be reassembled on the
5
+ * consumer's side.
6
6
  *
7
- * `grantExpiry`, `railFigure`, `railBalance` and `bar` each accept two shapes.
7
+ * **Every field is a `string`.** `grantExpiry`, `railFigure`, `railBalance` and
8
+ * `bar` are templates: plain strings marking each value's position with a
9
+ * `%placeholder%` -- `'Granted expires %date% · burns first'`. So the whole
10
+ * `labels` object is data all the way down, it survives the React Server Component
11
+ * boundary, and a Next App Router page can build it. There is no function form:
12
+ * it only ever worked from a Client Component and nothing in the type said so
13
+ * (B33-1278). A sentence needing logic a template cannot express -- a plural rule,
14
+ * an `Intl.ListFormat` call -- is computed in the consumer's own catalogue, where
15
+ * ICU already lives, and passed here finished.
8
16
  *
9
- * A **template** is a plain string marking the value's position with a
10
- * `{placeholder}` -- `'Granted expires {date} · burns first'`. It is data all
11
- * the way down, so the whole `labels` object survives the React Server Component
12
- * boundary and a Next App Router page can pass it. **This is the recommended
13
- * form**, and the one the defaults use.
14
- *
15
- * A **function** of the same values still works unchanged. It is never
16
- * serializable, so it only reaches the chip from a Client Component, but it is
17
- * the escape hatch when the sentence needs logic a template cannot express --
18
- * ICU plurals, an `Intl.ListFormat` call.
19
- *
20
- * In a template, a `{name}` the label does not define is left exactly as written
21
- * -- a locale whose copy genuinely contains braces is not mangled, and a
17
+ * In a template, a `%name%` the label does not define is left exactly as written
18
+ * -- a locale whose copy genuinely pairs two percent signs is not mangled, and a
22
19
  * misspelled placeholder shows up in the rendering rather than silently
23
20
  * blanking. A placeholder a template omits is simply not rendered: a locale that
24
21
  * does not need a figure is allowed not to name it.
22
+ *
23
+ * **The delimiter is `%name%`, not `{name}`** (B33-1277). A brace is ICU
24
+ * MessageFormat argument syntax, so a consumer storing these templates in its own
25
+ * next-intl catalogue could not read them back through its own formatter. It can
26
+ * now. `src/fill-template.ts` carries the reasoning, including why `%{name}` and
27
+ * `{{name}}` would both have kept the collision. A `{braced}` name in a template
28
+ * is ordinary text here and is passed through untouched.
25
29
  */
26
30
  export interface WalletChipLabels {
27
31
  /** Accessible name for the expanded chip's status region. Default "Wallet balance". */
28
32
  region: string;
29
33
  /** The chip header. Default "Wallet". */
30
34
  name: string;
31
- /** The unit rendered beside the figure. Default "tokens". */
35
+ /**
36
+ * The unit rendered beside the figure. Default "tokens".
37
+ *
38
+ * A single string, and so not count-dependent: a balance of one renders "1
39
+ * tokens" in English. That has been true since B33-849 and B33-1278 did not
40
+ * change it -- `unit` never had the function form, so dropping that form took
41
+ * nothing away here. It is also a state the chip is unlikely to reach: a
42
+ * one-token balance is an all-but-empty wallet. If a locale needs the unit to
43
+ * agree with the figure, the shape to add is a count-aware field, which is a
44
+ * decision and not a refactor; nothing in the package invents one meanwhile.
45
+ */
32
46
  unit: string;
33
47
  /** Screen-reader-only text in the figure when the balance is unknown. Default "Balance unknown". */
34
48
  balanceUnknown: string;
@@ -40,10 +54,9 @@ export interface WalletChipLabels {
40
54
  * The footer, around the preformatted expiry date. Supplied whole so the
41
55
  * consumer owns word order.
42
56
  *
43
- * Placeholder: `{date}`. Default (en) `Granted expires {date} · burns first`.
44
- * A function form `(date) => string` is also accepted.
57
+ * Placeholder: `%date%`. Default (en) `Granted expires %date% · burns first`.
45
58
  */
46
- grantExpiry: string | ((date: string) => string);
59
+ grantExpiry: string;
47
60
  /** The top-up button. Default "Top up". */
48
61
  topUp: string;
49
62
  /**
@@ -51,8 +64,7 @@ export interface WalletChipLabels {
51
64
  * (e.g. "4.6"). Both the visible text in the 56px rail and the figure that
52
65
  * `railBalance` is given.
53
66
  *
54
- * Placeholder: `{figure}`. Default (en) `{figure}k`. A function form
55
- * `(figure) => string` is also accepted.
67
+ * Placeholder: `%figure%`. Default (en) `%figure%k`.
56
68
  *
57
69
  * The figure itself is formatted for `locale` -- "4.6" in `en-US`, "4,6" in
58
70
  * `fr-FR` -- but the mark after it is a word, and locales disagree about it:
@@ -61,14 +73,13 @@ export interface WalletChipLabels {
61
73
  * template rather than a bare suffix, because a locale is entitled to put the
62
74
  * mark somewhere other than the end (B33-876).
63
75
  */
64
- railFigure: string | ((figure: string) => string);
76
+ railFigure: string;
65
77
  /**
66
78
  * The rail group's accessible name, around the abbreviated balance (e.g. "4.6k").
67
79
  *
68
- * Placeholder: `{balance}`. Default (en) `Wallet balance {balance}`. A function
69
- * form `(balance) => string` is also accepted.
80
+ * Placeholder: `%balance%`. Default (en) `Wallet balance %balance%`.
70
81
  */
71
- railBalance: string | ((balance: string) => string);
82
+ railBalance: string;
72
83
  /** The rail group's accessible name when the balance is unknown. Default "Wallet balance unknown". */
73
84
  railBalanceUnknown: string;
74
85
  /** Screen-reader-only marker on the rail when the balance is unknown. Default "unknown". */
@@ -78,22 +89,25 @@ export interface WalletChipLabels {
78
89
  * whole rather than concatenated from fragments -- the shape that cannot be
79
90
  * translated at all otherwise.
80
91
  *
81
- * Placeholders: `{granted}`, `{purchased}`, `{remaining}`. Default (en)
82
- * `{granted} granted and {purchased} purchased of {remaining} remaining`. A
83
- * function form `(granted, purchased, remaining) => string` is also accepted.
92
+ * Placeholders: `%granted%`, `%purchased%`, `%remaining%`. Default (en)
93
+ * `%granted% granted and %purchased% purchased of %remaining% remaining`.
84
94
  */
85
- bar: string | ((granted: string, purchased: string, remaining: string) => string);
95
+ bar: string;
86
96
  }
87
97
  /**
88
98
  * The English defaults. A consumer that passes nothing renders exactly what the
89
99
  * chip rendered before this prop existed, so the wallet baselines do not move.
90
100
  *
91
- * **Every field is a string.** That is what makes `labels={defaultWalletChipLabels}`
92
- * and `labels={{ ...defaultWalletChipLabels, name: 'Portefeuille' }}` -- the
93
- * pattern the specimen shows and this package's own advice recommends -- legal
94
- * from a Server Component. The four value-carrying defaults are templates, and
95
- * the rendered output is character-for-character what the function defaults
96
- * produced (B33-880).
101
+ * **Every field is a string, and now by construction.** That is what makes
102
+ * `labels={defaultWalletChipLabels}` and
103
+ * `labels={{ ...defaultWalletChipLabels, name: 'Portefeuille' }}` -- the pattern
104
+ * the specimen shows and this package's own advice recommends -- legal from a
105
+ * Server Component (B33-880). It used to be a runtime fact pinned by a test,
106
+ * because `WalletChipLabels` admitted functions on four fields and `satisfies`
107
+ * could not tell the two apart (B33-1029). With the union gone the compiler
108
+ * carries it: a function here is a type error at the `satisfies`, and
109
+ * `tests/wallet-chip-labels.type-test.ts` asserts the all-strings shape of the
110
+ * interface itself so the guarantee cannot be lost by widening one field.
97
111
  *
98
112
  * Frozen because it is a public export reaching four products on a version bump.
99
113
  * `const` protects the binding, not the contents: without the freeze, a consumer
@@ -116,11 +130,15 @@ export declare const defaultWalletChipLabels: Readonly<{
116
130
  bar: string;
117
131
  }>;
118
132
  /**
119
- * The labels as the render consumes them: the four value-carrying fields
120
- * normalised to functions, whichever shape the consumer supplied. The component
121
- * calls `t.bar(…)` either way and never learns which form it was given, so the
122
- * union costs the render nothing -- and, in particular, the lazy call that keeps
123
- * a consumer's `bar` off the degraded unknown path (B33-875) still works.
133
+ * The labels as the render consumes them: the four templates presented as
134
+ * functions of their values, so the component writes `t.bar(…)` and never handles
135
+ * a template itself.
136
+ *
137
+ * This is internal -- it is not exported from `src/index.ts` -- and it stays a
138
+ * function shape now that `WalletChipLabels` holds only strings, for two reasons.
139
+ * The interpolation has to happen somewhere, and one site per label is the whole
140
+ * of it; and the call is where B33-875's laziness lives, so a label for an element
141
+ * that does not render is not interpolated at all.
124
142
  */
125
143
  export interface ResolvedWalletChipLabels extends Omit<WalletChipLabels, 'grantExpiry' | 'railFigure' | 'railBalance' | 'bar'> {
126
144
  grantExpiry: (date: string) => string;
@@ -130,14 +148,14 @@ export interface ResolvedWalletChipLabels extends Omit<WalletChipLabels, 'grantE
130
148
  }
131
149
  /**
132
150
  * Merge a consumer's partial `labels` onto the English defaults, treating an
133
- * explicitly-`undefined` field as unsupplied, and normalise the four
134
- * value-carrying fields to functions.
151
+ * explicitly-`undefined` field as unsupplied, and present the four templates as
152
+ * functions of their values.
135
153
  *
136
154
  * A next-intl `t('...')` returns `undefined` on a missed key, so
137
155
  * `{ name: undefined }` is what one missing translation out of thirteen looks like
138
156
  * at the call site -- it must fall back to English, not overwrite the default
139
- * with `undefined` and blank the header, unname the button, or crash on
140
- * `t.bar(...)` (B33-873). A plain object spread copies the `undefined` across; a
141
- * per-field skip does not.
157
+ * with `undefined` and blank the header, unname the button, or hand
158
+ * `fillTemplate` an `undefined` template (B33-873). A plain object spread copies
159
+ * the `undefined` across; a per-field skip does not.
142
160
  */
143
161
  export declare function resolveWalletChipLabels(labels?: Partial<WalletChipLabels>): ResolvedWalletChipLabels;
@@ -1,3 +1,4 @@
1
+ import { fillTemplate as n } from "../../fill-template.js";
1
2
  const c = Object.freeze({
2
3
  region: "Wallet balance",
3
4
  name: "Wallet",
@@ -5,34 +6,28 @@ const c = Object.freeze({
5
6
  balanceUnknown: "Balance unknown",
6
7
  granted: "Granted",
7
8
  purchased: "Purchased",
8
- grantExpiry: "Granted expires {date} · burns first",
9
+ grantExpiry: "Granted expires %date% · burns first",
9
10
  topUp: "Top up",
10
- railFigure: "{figure}k",
11
- railBalance: "Wallet balance {balance}",
11
+ railFigure: "%figure%k",
12
+ railBalance: "Wallet balance %balance%",
12
13
  railBalanceUnknown: "Wallet balance unknown",
13
14
  railUnknown: "unknown",
14
- bar: "{granted} granted and {purchased} purchased of {remaining} remaining"
15
+ bar: "%granted% granted and %purchased% purchased of %remaining% remaining"
15
16
  });
16
- function r(t, e) {
17
- return t.replace(
18
- /\{(\w+)\}/g,
19
- (n, a) => Object.hasOwn(e, a) ? e[a] ?? n : n
20
- );
21
- }
22
- function o(t) {
23
- const e = Object.fromEntries(
24
- Object.entries(t ?? {}).filter(([, a]) => a !== void 0)
25
- ), n = { ...c, ...e };
17
+ function p(r) {
18
+ const l = Object.fromEntries(
19
+ Object.entries(r ?? {}).filter(([, e]) => e !== void 0)
20
+ ), a = { ...c, ...l };
26
21
  return {
27
- ...n,
28
- grantExpiry: (a) => typeof n.grantExpiry == "function" ? n.grantExpiry(a) : r(n.grantExpiry, { date: a }),
29
- railFigure: (a) => typeof n.railFigure == "function" ? n.railFigure(a) : r(n.railFigure, { figure: a }),
30
- railBalance: (a) => typeof n.railBalance == "function" ? n.railBalance(a) : r(n.railBalance, { balance: a }),
31
- bar: (a, l, i) => typeof n.bar == "function" ? n.bar(a, l, i) : r(n.bar, { granted: a, purchased: l, remaining: i })
22
+ ...a,
23
+ grantExpiry: (e) => n(a.grantExpiry, { date: e }),
24
+ railFigure: (e) => n(a.railFigure, { figure: e }),
25
+ railBalance: (e) => n(a.railBalance, { balance: e }),
26
+ bar: (e, t, i) => n(a.bar, { granted: e, purchased: t, remaining: i })
32
27
  };
33
28
  }
34
29
  export {
35
30
  c as defaultWalletChipLabels,
36
- o as resolveWalletChipLabels
31
+ p as resolveWalletChipLabels
37
32
  };
38
33
  //# sourceMappingURL=labels.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"labels.js","sources":["../../../src/components/WalletChip/labels.ts"],"sourcesContent":["// The chip is the only component in the package that renders words. Every one\n// of them lives here as a prop with an English default, so a French-default\n// consumer (the first two are) can translate the chip without the package\n// taking an i18n dependency -- CLAUDE.md, \"a component here renders what it is\n// given\". This is the same argument grantExpires and burnNote already made,\n// applied to the strings that were still English literals in the render.\n//\n// Three of those strings carry a value, and word order is not a property of\n// English: \"Granted expires 30 Sep\" puts the date where English puts it, and a\n// translation cannot be reassembled from fragments on the consumer's side. The\n// shape that solves that and nothing else is a function -- and a function is\n// the one thing that cannot be a prop of a Client Component rendered from a\n// React Server Component. React's Flight serializer walks the whole prop graph\n// and throws on any function it finds, nested or not, in production as well as\n// in development:\n//\n// Functions cannot be passed directly to Client Components unless you\n// explicitly expose it by marking it with \"use server\".\n//\n// `AppShell.wallet` is a ReactNode slot, so it is the *consumer's page* that\n// constructs `<WalletChip …/>`, and a Server Component page is the default way\n// the shell is meant to be used. A labels object that cannot cross that\n// boundary is a labels object most consumers cannot pass (B33-880).\n//\n// So each of the three takes either shape. The string form spells the value's\n// position with a `{placeholder}`, which is data all the way down and crosses\n// the boundary intact; it is the form the defaults use and the form the\n// specimen and the CHANGELOG demonstrate. The function form still works\n// unchanged, for a Client Component consumer that wants a real function --\n// next-intl's ICU plurals, an Intl.ListFormat call -- and that is a consumer\n// already on the client, where functions are ordinary props.\n//\n// B33-876 added a fourth of the same kind, `railFigure`. The rail's balance is\n// a number the `locale` prop formats -- \"4.6\" or \"4,6\" -- followed by a mark\n// that is a word and not a number: English \"k\", German \"Tsd.\", and French with\n// a space in front of it that English does not have. Intl cannot supply it (its\n// own compact notation renders German four-digit balances unabbreviated, which\n// does not fit a 56px rail -- see format.ts), so it belongs here, in the same\n// template shape, for the same reason: the consumer owns where the mark goes.\n\n/**\n * Every user-facing string the chip renders. Supplied whole -- the strings that\n * carry a value take a `{placeholder}` template (or a function) rather than\n * fragments, because word order is not a property of English and cannot be\n * reassembled on the consumer's side.\n *\n * `grantExpiry`, `railFigure`, `railBalance` and `bar` each accept two shapes.\n *\n * A **template** is a plain string marking the value's position with a\n * `{placeholder}` -- `'Granted expires {date} · burns first'`. It is data all\n * the way down, so the whole `labels` object survives the React Server Component\n * boundary and a Next App Router page can pass it. **This is the recommended\n * form**, and the one the defaults use.\n *\n * A **function** of the same values still works unchanged. It is never\n * serializable, so it only reaches the chip from a Client Component, but it is\n * the escape hatch when the sentence needs logic a template cannot express --\n * ICU plurals, an `Intl.ListFormat` call.\n *\n * In a template, a `{name}` the label does not define is left exactly as written\n * -- a locale whose copy genuinely contains braces is not mangled, and a\n * misspelled placeholder shows up in the rendering rather than silently\n * blanking. A placeholder a template omits is simply not rendered: a locale that\n * does not need a figure is allowed not to name it.\n */\nexport interface WalletChipLabels {\n /** Accessible name for the expanded chip's status region. Default \"Wallet balance\". */\n region: string\n /** The chip header. Default \"Wallet\". */\n name: string\n /** The unit rendered beside the figure. Default \"tokens\". */\n unit: string\n /** Screen-reader-only text in the figure when the balance is unknown. Default \"Balance unknown\". */\n balanceUnknown: string\n /** The granted side of the split -- granted burns first. Default \"Granted\". */\n granted: string\n /** The purchased side of the split -- purchased never expires. Default \"Purchased\". */\n purchased: string\n /**\n * The footer, around the preformatted expiry date. Supplied whole so the\n * consumer owns word order.\n *\n * Placeholder: `{date}`. Default (en) `Granted expires {date} · burns first`.\n * A function form `(date) => string` is also accepted.\n */\n grantExpiry: string | ((date: string) => string)\n /** The top-up button. Default \"Top up\". */\n topUp: string\n /**\n * The rail's abbreviated balance, around the one-decimal-thousand figure\n * (e.g. \"4.6\"). Both the visible text in the 56px rail and the figure that\n * `railBalance` is given.\n *\n * Placeholder: `{figure}`. Default (en) `{figure}k`. A function form\n * `(figure) => string` is also accepted.\n *\n * The figure itself is formatted for `locale` -- \"4.6\" in `en-US`, \"4,6\" in\n * `fr-FR` -- but the mark after it is a word, and locales disagree about it:\n * French writes \"4,6 k\" with a space, German abbreviates a thousand \"Tsd.\".\n * So the mark is a label like every other word the chip renders, and a\n * template rather than a bare suffix, because a locale is entitled to put the\n * mark somewhere other than the end (B33-876).\n */\n railFigure: string | ((figure: string) => string)\n /**\n * The rail group's accessible name, around the abbreviated balance (e.g. \"4.6k\").\n *\n * Placeholder: `{balance}`. Default (en) `Wallet balance {balance}`. A function\n * form `(balance) => string` is also accepted.\n */\n railBalance: string | ((balance: string) => string)\n /** The rail group's accessible name when the balance is unknown. Default \"Wallet balance unknown\". */\n railBalanceUnknown: string\n /** Screen-reader-only marker on the rail when the balance is unknown. Default \"unknown\". */\n railUnknown: string\n /**\n * The bar's accessible label, around the three preformatted figures. Assembled\n * whole rather than concatenated from fragments -- the shape that cannot be\n * translated at all otherwise.\n *\n * Placeholders: `{granted}`, `{purchased}`, `{remaining}`. Default (en)\n * `{granted} granted and {purchased} purchased of {remaining} remaining`. A\n * function form `(granted, purchased, remaining) => string` is also accepted.\n */\n bar: string | ((granted: string, purchased: string, remaining: string) => string)\n}\n\n/**\n * The English defaults. A consumer that passes nothing renders exactly what the\n * chip rendered before this prop existed, so the wallet baselines do not move.\n *\n * **Every field is a string.** That is what makes `labels={defaultWalletChipLabels}`\n * and `labels={{ ...defaultWalletChipLabels, name: 'Portefeuille' }}` -- the\n * pattern the specimen shows and this package's own advice recommends -- legal\n * from a Server Component. The four value-carrying defaults are templates, and\n * the rendered output is character-for-character what the function defaults\n * produced (B33-880).\n *\n * Frozen because it is a public export reaching four products on a version bump.\n * `const` protects the binding, not the contents: without the freeze, a consumer\n * aliasing it by reference -- `const mine = defaultWalletChipLabels; mine.name =\n * 'Portefeuille'` -- would silently rename every chip in the process (B33-877).\n */\nexport const defaultWalletChipLabels = Object.freeze({\n region: 'Wallet balance',\n name: 'Wallet',\n unit: 'tokens',\n balanceUnknown: 'Balance unknown',\n granted: 'Granted',\n purchased: 'Purchased',\n grantExpiry: 'Granted expires {date} · burns first',\n topUp: 'Top up',\n railFigure: '{figure}k',\n railBalance: 'Wallet balance {balance}',\n railBalanceUnknown: 'Wallet balance unknown',\n railUnknown: 'unknown',\n bar: '{granted} granted and {purchased} purchased of {remaining} remaining',\n} satisfies WalletChipLabels)\n\n/**\n * The labels as the render consumes them: the four value-carrying fields\n * normalised to functions, whichever shape the consumer supplied. The component\n * calls `t.bar(…)` either way and never learns which form it was given, so the\n * union costs the render nothing -- and, in particular, the lazy call that keeps\n * a consumer's `bar` off the degraded unknown path (B33-875) still works.\n */\nexport interface ResolvedWalletChipLabels\n extends Omit<WalletChipLabels, 'grantExpiry' | 'railFigure' | 'railBalance' | 'bar'> {\n grantExpiry: (date: string) => string\n railFigure: (figure: string) => string\n railBalance: (balance: string) => string\n bar: (granted: string, purchased: string, remaining: string) => string\n}\n\n/**\n * Fill a template's `{placeholder}`s from `values`.\n *\n * A name not in `values` is left verbatim, so misspelled or deliberate braces\n * survive to the rendering rather than disappearing. The replacement runs\n * through a callback, so a `$` in a formatted figure is a `$` and not a\n * `String.prototype.replace` substitution pattern.\n */\nfunction fillTemplate(template: string, values: Record<string, string>): string {\n return template.replace(/\\{(\\w+)\\}/g, (whole, key: string) =>\n Object.hasOwn(values, key) ? (values[key] ?? whole) : whole,\n )\n}\n\n/**\n * Merge a consumer's partial `labels` onto the English defaults, treating an\n * explicitly-`undefined` field as unsupplied, and normalise the four\n * value-carrying fields to functions.\n *\n * A next-intl `t('...')` returns `undefined` on a missed key, so\n * `{ name: undefined }` is what one missing translation out of thirteen looks like\n * at the call site -- it must fall back to English, not overwrite the default\n * with `undefined` and blank the header, unname the button, or crash on\n * `t.bar(...)` (B33-873). A plain object spread copies the `undefined` across; a\n * per-field skip does not.\n */\nexport function resolveWalletChipLabels(labels?: Partial<WalletChipLabels>): ResolvedWalletChipLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<WalletChipLabels>\n const merged = { ...defaultWalletChipLabels, ...supplied }\n return {\n ...merged,\n grantExpiry: (date) =>\n typeof merged.grantExpiry === 'function'\n ? merged.grantExpiry(date)\n : fillTemplate(merged.grantExpiry, { date }),\n railFigure: (figure) =>\n typeof merged.railFigure === 'function'\n ? merged.railFigure(figure)\n : fillTemplate(merged.railFigure, { figure }),\n railBalance: (balance) =>\n typeof merged.railBalance === 'function'\n ? merged.railBalance(balance)\n : fillTemplate(merged.railBalance, { balance }),\n bar: (granted, purchased, remaining) =>\n typeof merged.bar === 'function'\n ? merged.bar(granted, purchased, remaining)\n : fillTemplate(merged.bar, { granted, purchased, remaining }),\n }\n}\n"],"names":["defaultWalletChipLabels","fillTemplate","template","values","whole","key","resolveWalletChipLabels","labels","supplied","value","merged","date","figure","balance","granted","purchased","remaining"],"mappings":"AA+IO,MAAMA,IAA0B,OAAO,OAAO;AAAA,EACnD,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,WAAW;AAAA,EACX,aAAa;AAAA,EACb,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,KAAK;AACP,CAA4B;AAyB5B,SAASC,EAAaC,GAAkBC,GAAwC;AAC9E,SAAOD,EAAS;AAAA,IAAQ;AAAA,IAAc,CAACE,GAAOC,MAC5C,OAAO,OAAOF,GAAQE,CAAG,IAAKF,EAAOE,CAAG,KAAKD,IAASA;AAAA,EAAA;AAE1D;AAcO,SAASE,EAAwBC,GAA8D;AACpG,QAAMC,IAAW,OAAO;AAAA,IACtB,OAAO,QAAQD,KAAU,EAAE,EAAE,OAAO,CAAC,CAAA,EAAGE,CAAK,MAAMA,MAAU,MAAS;AAAA,EAAA,GAElEC,IAAS,EAAE,GAAGV,GAAyB,GAAGQ,EAAA;AAChD,SAAO;AAAA,IACL,GAAGE;AAAA,IACH,aAAa,CAACC,MACZ,OAAOD,EAAO,eAAgB,aAC1BA,EAAO,YAAYC,CAAI,IACvBV,EAAaS,EAAO,aAAa,EAAE,MAAAC,GAAM;AAAA,IAC/C,YAAY,CAACC,MACX,OAAOF,EAAO,cAAe,aACzBA,EAAO,WAAWE,CAAM,IACxBX,EAAaS,EAAO,YAAY,EAAE,QAAAE,GAAQ;AAAA,IAChD,aAAa,CAACC,MACZ,OAAOH,EAAO,eAAgB,aAC1BA,EAAO,YAAYG,CAAO,IAC1BZ,EAAaS,EAAO,aAAa,EAAE,SAAAG,GAAS;AAAA,IAClD,KAAK,CAACC,GAASC,GAAWC,MACxB,OAAON,EAAO,OAAQ,aAClBA,EAAO,IAAII,GAASC,GAAWC,CAAS,IACxCf,EAAaS,EAAO,KAAK,EAAE,SAAAI,GAAS,WAAAC,GAAW,WAAAC,EAAA,CAAW;AAAA,EAAA;AAEpE;"}
1
+ {"version":3,"file":"labels.js","sources":["../../../src/components/WalletChip/labels.ts"],"sourcesContent":["// The chip is the only component in the package that renders words. Every one\n// of them lives here as a prop with an English default, so a French-default\n// consumer (the first two are) can translate the chip without the package\n// taking an i18n dependency -- CLAUDE.md, \"a component here renders what it is\n// given\". This is the same argument grantExpires and burnNote already made,\n// applied to the strings that were still English literals in the render.\n//\n// Three of those strings carry a value, and word order is not a property of\n// English: \"Granted expires 30 Sep\" puts the date where English puts it, and a\n// translation cannot be reassembled from fragments on the consumer's side. They\n// are supplied whole, as a template that spells the value's position with a\n// `%placeholder%`.\n//\n// ── Every field is a string, and that is the whole type (B33-1278) ───────────\n//\n// Each of those three was once `string | ((…) => string)`. The function half is\n// gone, and the reason is that it could not be used by the consumers this package\n// has. A function is the one thing that cannot be a prop of a Client Component\n// rendered from a React Server Component: React's Flight serializer walks the\n// whole prop graph and throws on any function it finds, nested or not, in\n// production as well as in development:\n//\n// Functions cannot be passed directly to Client Components unless you\n// explicitly expose it by marking it with \"use server\".\n//\n// `WalletChip` is a client module and `AppShell.wallet` is a ReactNode slot, so\n// it is the *consumer's page* that constructs `<WalletChip …/>` -- and under the\n// App Router that page is a Server Component by default, which is how both\n// consumers render it, because reading the balance is a server read. So the\n// function half was type-legal and fatal in production for the only way anyone\n// uses the component, and nothing in the type said so: `tsc` accepted it, the dev\n// server accepted it, and it threw at serialisation time (B33-880 for the first\n// half of this, B33-1278 for the rest). Score covered the hole from outside, with\n// a runtime `typeof raw === 'string'` guard and five tests walking the prop graph,\n// because -- in its own words -- neither `tsc` nor the dev server will.\n//\n// What the function form was *for* was logic a template cannot express: ICU\n// plurals, an `Intl.ListFormat` call. That capability is not lost, it moves to\n// where ICU already lives. A consumer selecting a plural form does the selection\n// in its own catalogue and passes this package the finished sentence, which is\n// what both consumers already do with next-intl. No default label in this package\n// is count-dependent, so no default needed it (see `unit` below).\n//\n// B33-876 added a fourth of the same kind, `railFigure`. The rail's balance is\n// a number the `locale` prop formats -- \"4.6\" or \"4,6\" -- followed by a mark\n// that is a word and not a number: English \"k\", German \"Tsd.\", and French with\n// a space in front of it that English does not have. Intl cannot supply it (its\n// own compact notation renders German four-digit balances unabbreviated, which\n// does not fit a 56px rail -- see format.ts), so it belongs here, in the same\n// template shape, for the same reason: the consumer owns where the mark goes.\n\nimport { fillTemplate } from '../../fill-template'\n\n/**\n * Every user-facing string the chip renders. Supplied whole -- the strings that\n * carry a value take a `%placeholder%` template rather than fragments, because\n * word order is not a property of English and cannot be reassembled on the\n * consumer's side.\n *\n * **Every field is a `string`.** `grantExpiry`, `railFigure`, `railBalance` and\n * `bar` are templates: plain strings marking each value's position with a\n * `%placeholder%` -- `'Granted expires %date% · burns first'`. So the whole\n * `labels` object is data all the way down, it survives the React Server Component\n * boundary, and a Next App Router page can build it. There is no function form:\n * it only ever worked from a Client Component and nothing in the type said so\n * (B33-1278). A sentence needing logic a template cannot express -- a plural rule,\n * an `Intl.ListFormat` call -- is computed in the consumer's own catalogue, where\n * ICU already lives, and passed here finished.\n *\n * In a template, a `%name%` the label does not define is left exactly as written\n * -- a locale whose copy genuinely pairs two percent signs is not mangled, and a\n * misspelled placeholder shows up in the rendering rather than silently\n * blanking. A placeholder a template omits is simply not rendered: a locale that\n * does not need a figure is allowed not to name it.\n *\n * **The delimiter is `%name%`, not `{name}`** (B33-1277). A brace is ICU\n * MessageFormat argument syntax, so a consumer storing these templates in its own\n * next-intl catalogue could not read them back through its own formatter. It can\n * now. `src/fill-template.ts` carries the reasoning, including why `%{name}` and\n * `{{name}}` would both have kept the collision. A `{braced}` name in a template\n * is ordinary text here and is passed through untouched.\n */\nexport interface WalletChipLabels {\n /** Accessible name for the expanded chip's status region. Default \"Wallet balance\". */\n region: string\n /** The chip header. Default \"Wallet\". */\n name: string\n /**\n * The unit rendered beside the figure. Default \"tokens\".\n *\n * A single string, and so not count-dependent: a balance of one renders \"1\n * tokens\" in English. That has been true since B33-849 and B33-1278 did not\n * change it -- `unit` never had the function form, so dropping that form took\n * nothing away here. It is also a state the chip is unlikely to reach: a\n * one-token balance is an all-but-empty wallet. If a locale needs the unit to\n * agree with the figure, the shape to add is a count-aware field, which is a\n * decision and not a refactor; nothing in the package invents one meanwhile.\n */\n unit: string\n /** Screen-reader-only text in the figure when the balance is unknown. Default \"Balance unknown\". */\n balanceUnknown: string\n /** The granted side of the split -- granted burns first. Default \"Granted\". */\n granted: string\n /** The purchased side of the split -- purchased never expires. Default \"Purchased\". */\n purchased: string\n /**\n * The footer, around the preformatted expiry date. Supplied whole so the\n * consumer owns word order.\n *\n * Placeholder: `%date%`. Default (en) `Granted expires %date% · burns first`.\n */\n grantExpiry: string\n /** The top-up button. Default \"Top up\". */\n topUp: string\n /**\n * The rail's abbreviated balance, around the one-decimal-thousand figure\n * (e.g. \"4.6\"). Both the visible text in the 56px rail and the figure that\n * `railBalance` is given.\n *\n * Placeholder: `%figure%`. Default (en) `%figure%k`.\n *\n * The figure itself is formatted for `locale` -- \"4.6\" in `en-US`, \"4,6\" in\n * `fr-FR` -- but the mark after it is a word, and locales disagree about it:\n * French writes \"4,6 k\" with a space, German abbreviates a thousand \"Tsd.\".\n * So the mark is a label like every other word the chip renders, and a\n * template rather than a bare suffix, because a locale is entitled to put the\n * mark somewhere other than the end (B33-876).\n */\n railFigure: string\n /**\n * The rail group's accessible name, around the abbreviated balance (e.g. \"4.6k\").\n *\n * Placeholder: `%balance%`. Default (en) `Wallet balance %balance%`.\n */\n railBalance: string\n /** The rail group's accessible name when the balance is unknown. Default \"Wallet balance unknown\". */\n railBalanceUnknown: string\n /** Screen-reader-only marker on the rail when the balance is unknown. Default \"unknown\". */\n railUnknown: string\n /**\n * The bar's accessible label, around the three preformatted figures. Assembled\n * whole rather than concatenated from fragments -- the shape that cannot be\n * translated at all otherwise.\n *\n * Placeholders: `%granted%`, `%purchased%`, `%remaining%`. Default (en)\n * `%granted% granted and %purchased% purchased of %remaining% remaining`.\n */\n bar: string\n}\n\n/**\n * The English defaults. A consumer that passes nothing renders exactly what the\n * chip rendered before this prop existed, so the wallet baselines do not move.\n *\n * **Every field is a string, and now by construction.** That is what makes\n * `labels={defaultWalletChipLabels}` and\n * `labels={{ ...defaultWalletChipLabels, name: 'Portefeuille' }}` -- the pattern\n * the specimen shows and this package's own advice recommends -- legal from a\n * Server Component (B33-880). It used to be a runtime fact pinned by a test,\n * because `WalletChipLabels` admitted functions on four fields and `satisfies`\n * could not tell the two apart (B33-1029). With the union gone the compiler\n * carries it: a function here is a type error at the `satisfies`, and\n * `tests/wallet-chip-labels.type-test.ts` asserts the all-strings shape of the\n * interface itself so the guarantee cannot be lost by widening one field.\n *\n * Frozen because it is a public export reaching four products on a version bump.\n * `const` protects the binding, not the contents: without the freeze, a consumer\n * aliasing it by reference -- `const mine = defaultWalletChipLabels; mine.name =\n * 'Portefeuille'` -- would silently rename every chip in the process (B33-877).\n */\nexport const defaultWalletChipLabels = Object.freeze({\n region: 'Wallet balance',\n name: 'Wallet',\n unit: 'tokens',\n balanceUnknown: 'Balance unknown',\n granted: 'Granted',\n purchased: 'Purchased',\n grantExpiry: 'Granted expires %date% · burns first',\n topUp: 'Top up',\n railFigure: '%figure%k',\n railBalance: 'Wallet balance %balance%',\n railBalanceUnknown: 'Wallet balance unknown',\n railUnknown: 'unknown',\n bar: '%granted% granted and %purchased% purchased of %remaining% remaining',\n} satisfies WalletChipLabels)\n\n/**\n * The labels as the render consumes them: the four templates presented as\n * functions of their values, so the component writes `t.bar(…)` and never handles\n * a template itself.\n *\n * This is internal -- it is not exported from `src/index.ts` -- and it stays a\n * function shape now that `WalletChipLabels` holds only strings, for two reasons.\n * The interpolation has to happen somewhere, and one site per label is the whole\n * of it; and the call is where B33-875's laziness lives, so a label for an element\n * that does not render is not interpolated at all.\n */\nexport interface ResolvedWalletChipLabels\n extends Omit<WalletChipLabels, 'grantExpiry' | 'railFigure' | 'railBalance' | 'bar'> {\n grantExpiry: (date: string) => string\n railFigure: (figure: string) => string\n railBalance: (balance: string) => string\n bar: (granted: string, purchased: string, remaining: string) => string\n}\n\n/**\n * Merge a consumer's partial `labels` onto the English defaults, treating an\n * explicitly-`undefined` field as unsupplied, and present the four templates as\n * functions of their values.\n *\n * A next-intl `t('...')` returns `undefined` on a missed key, so\n * `{ name: undefined }` is what one missing translation out of thirteen looks like\n * at the call site -- it must fall back to English, not overwrite the default\n * with `undefined` and blank the header, unname the button, or hand\n * `fillTemplate` an `undefined` template (B33-873). A plain object spread copies\n * the `undefined` across; a per-field skip does not.\n */\nexport function resolveWalletChipLabels(labels?: Partial<WalletChipLabels>): ResolvedWalletChipLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<WalletChipLabels>\n const merged = { ...defaultWalletChipLabels, ...supplied }\n return {\n ...merged,\n grantExpiry: (date) => fillTemplate(merged.grantExpiry, { date }),\n railFigure: (figure) => fillTemplate(merged.railFigure, { figure }),\n railBalance: (balance) => fillTemplate(merged.railBalance, { balance }),\n bar: (granted, purchased, remaining) => fillTemplate(merged.bar, { granted, purchased, remaining }),\n }\n}\n"],"names":["defaultWalletChipLabels","resolveWalletChipLabels","labels","supplied","value","merged","date","fillTemplate","figure","balance","granted","purchased","remaining"],"mappings":";AA0KO,MAAMA,IAA0B,OAAO,OAAO;AAAA,EACnD,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,WAAW;AAAA,EACX,aAAa;AAAA,EACb,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,KAAK;AACP,CAA4B;AAiCrB,SAASC,EAAwBC,GAA8D;AACpG,QAAMC,IAAW,OAAO;AAAA,IACtB,OAAO,QAAQD,KAAU,EAAE,EAAE,OAAO,CAAC,CAAA,EAAGE,CAAK,MAAMA,MAAU,MAAS;AAAA,EAAA,GAElEC,IAAS,EAAE,GAAGL,GAAyB,GAAGG,EAAA;AAChD,SAAO;AAAA,IACL,GAAGE;AAAA,IACH,aAAa,CAACC,MAASC,EAAaF,EAAO,aAAa,EAAE,MAAAC,GAAM;AAAA,IAChE,YAAY,CAACE,MAAWD,EAAaF,EAAO,YAAY,EAAE,QAAAG,GAAQ;AAAA,IAClE,aAAa,CAACC,MAAYF,EAAaF,EAAO,aAAa,EAAE,SAAAI,GAAS;AAAA,IACtE,KAAK,CAACC,GAASC,GAAWC,MAAcL,EAAaF,EAAO,KAAK,EAAE,SAAAK,GAAS,WAAAC,GAAW,WAAAC,EAAA,CAAW;AAAA,EAAA;AAEtG;"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Fill a template's `%placeholder%`s from `values`.
3
+ *
4
+ * A name not in `values` is left verbatim, so a misspelling, a deliberate pair
5
+ * of percent signs, or a `{braced}` name meant for someone else's formatter all
6
+ * survive to the rendering rather than disappearing. The replacement runs
7
+ * through a callback, so a `$` in a formatted figure is a `$` and not a
8
+ * `String.prototype.replace` substitution pattern.
9
+ */
10
+ export declare function fillTemplate(template: string, values: Record<string, string>): string;
@@ -0,0 +1,10 @@
1
+ function c(r, t) {
2
+ return r.replace(
3
+ /%(\w+)%/g,
4
+ (e, n) => Object.hasOwn(t, n) ? t[n] ?? e : e
5
+ );
6
+ }
7
+ export {
8
+ c as fillTemplate
9
+ };
10
+ //# sourceMappingURL=fill-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fill-template.js","sources":["../src/fill-template.ts"],"sourcesContent":["/*\n * The one `%placeholder%` filler, shared by every labels module.\n *\n * Flat at `src/` root beside `anchored-overlay.ts` and `nav-height-budget.ts`,\n * the precedent for logic shared across components, and absent from\n * `src/index.ts` -- consumers get components, not helpers.\n *\n * It lived privately inside `WalletChip/labels.ts` until B33-494 needed the same\n * shape for the NotificationCenter's `%source% · %time%` line. Copying it would\n * have been the third labels module re-deciding a contract two had already\n * settled, which is exactly what B33-1007 is open about: that ticket found\n * `AppSwitcher`'s labels diverging from `WalletChip`'s in three ways while its\n * own comment claimed parity, and `CLAUDE.md` names the outcome -- \"two\n * almost-identical components is the failure mode this whole repo exists to\n * end\". A second copy of this function would be a fourth dialect of the same\n * five lines, differing the day one of them is fixed.\n *\n * ── Why the delimiter is `%name%` and not `{name}` (B33-1277) ────────────────\n *\n * It was `{name}` until B33-1277, and `{\\w+}` is *exactly* ICU MessageFormat\n * argument syntax -- the syntax next-intl, FormatJS and every other ICU-backed\n * library in the Next.js ecosystem parses. So a consumer that stores these\n * default templates in the message catalogue it already has cannot read them\n * back through the function it already uses: ICU sees `{date}`, treats it as an\n * argument it was not given, and renders an error string where the sentence\n * should be. Score had to write a second accessor around its own formatter to\n * get the raw string out (`boostscore/components/app-shell/wallet-chip.tsx`) --\n * two accessors for one label bag, split on which fields happen to carry a\n * value, with the wrong choice showing formatter output in the sidebar.\n *\n * The fix is a delimiter holding no brace at all, which rules out both spellings\n * the ticket floated:\n *\n * - `%{date}` still holds `{date}`. ICU does not read `%` as an escape, so it\n * parses the argument and the failure is unchanged. The ticket's aside that\n * \"ICU ignores it\" is the one claim in it that does not hold.\n * - `{{date}}` is worse, as the ticket itself says: `{` is not a legal first\n * character of an ICU argument name, so such a catalogue fails to parse at\n * all rather than spoiling one sentence.\n *\n * `%name%` also clears every other formatter a Boost33 consumer might put copy\n * through, because all of them claim the double brace: i18next, Mustache and\n * Handlebars, and Vue's, Angular's and Svelte's template syntax. And it reads as\n * one unit in a translator's editor, being delimited at both ends -- gettext's\n * `%(name)s` leaves a bare `s` against the following word, and translators drop\n * it.\n *\n * No default in this package contains a `%`, so none of its own copy can be\n * mistaken for a placeholder; and an unknown name is left verbatim (below), so a\n * locale whose prose genuinely pairs two percent signs is not mangled, and\n * neither is an ICU `{argument}` a consumer means its own formatter to fill.\n */\n\n/**\n * Fill a template's `%placeholder%`s from `values`.\n *\n * A name not in `values` is left verbatim, so a misspelling, a deliberate pair\n * of percent signs, or a `{braced}` name meant for someone else's formatter all\n * survive to the rendering rather than disappearing. The replacement runs\n * through a callback, so a `$` in a formatted figure is a `$` and not a\n * `String.prototype.replace` substitution pattern.\n */\nexport function fillTemplate(template: string, values: Record<string, string>): string {\n return template.replace(/%(\\w+)%/g, (whole, key: string) =>\n Object.hasOwn(values, key) ? (values[key] ?? whole) : whole,\n )\n}\n"],"names":["fillTemplate","template","values","whole","key"],"mappings":"AA8DO,SAASA,EAAaC,GAAkBC,GAAwC;AACrF,SAAOD,EAAS;AAAA,IAAQ;AAAA,IAAY,CAACE,GAAOC,MAC1C,OAAO,OAAOF,GAAQE,CAAG,IAAKF,EAAOE,CAAG,KAAKD,IAASA;AAAA,EAAA;AAE1D;"}
package/dist/index.d.ts CHANGED
@@ -6,12 +6,12 @@ export { Wordmark } from './components/Wordmark/Wordmark';
6
6
  export type { WordmarkProps } from './components/Wordmark/Wordmark';
7
7
  export { Boost33Mark, BOOST33_MARK_MIN_LEGIBLE_PX } from './components/Boost33Mark/Boost33Mark';
8
8
  export type { Boost33MarkProps } from './components/Boost33Mark/Boost33Mark';
9
- export { ProductMark, PRODUCT_SLUGS } from './components/ProductMark/ProductMark';
10
- export type { ProductMarkProps, ProductSlug } from './components/ProductMark/ProductMark';
9
+ export { ProductMark, hasProductMark } from './components/ProductMark/ProductMark';
10
+ export type { ProductMarkProps } from './components/ProductMark/ProductMark';
11
11
  export { NavItem } from './components/NavItem/NavItem';
12
12
  export type { NavItemProps } from './components/NavItem/NavItem';
13
13
  export { WalletChip } from './components/WalletChip/WalletChip';
14
- export type { WalletChipProps } from './components/WalletChip/WalletChip';
14
+ export type { WalletChipProps, WalletChipKnownProps, WalletChipUnknownProps } from './components/WalletChip/WalletChip';
15
15
  export { defaultWalletChipLabels } from './components/WalletChip/labels';
16
16
  export type { WalletChipLabels } from './components/WalletChip/labels';
17
17
  export { TopBar } from './components/TopBar/TopBar';
@@ -24,5 +24,11 @@ export { AppSwitcher } from './components/AppSwitcher/AppSwitcher';
24
24
  export type { AppSwitcherProps, AppSwitcherProduct, AppSwitcherHub, AppSwitcherAccount, } from './components/AppSwitcher/AppSwitcher';
25
25
  export { defaultAppSwitcherLabels } from './components/AppSwitcher/labels';
26
26
  export type { AppSwitcherLabels } from './components/AppSwitcher/labels';
27
- export { NAV_HEIGHT_BUDGET_PX, NAV_SECTION_HEADER_PX, NAV_ITEM_PX, NAV_SECTION_GAP_PX, NAV_FOCUS_GUTTER_PX, NAV_SHELL_FIXED_PX, NAV_SHELL_PACKAGE_FIXED_PX, NAV_SHELL_CONSUMER_SLOTS_PX, checkNavHeightBudget, measureNavHeight, navContentHeight, navBudgetForViewport, } from './nav-height-budget';
27
+ export { DotMatrixLoader } from './components/DotMatrixLoader/DotMatrixLoader';
28
+ export type { DotMatrixLoaderProps } from './components/DotMatrixLoader/DotMatrixLoader';
29
+ export { LaunchLoader } from './components/LaunchLoader/LaunchLoader';
30
+ export type { LaunchLoaderProps } from './components/LaunchLoader/LaunchLoader';
31
+ export { launchBackdropCss, launchGround, launchSolid } from './launch-gradient';
32
+ export type { LaunchGradientStops } from './launch-gradient';
33
+ export { checkNavHeightBudget, navContentHeight, navBudgetForViewport } from './nav-height-budget';
28
34
  export type { NavHeightBudget, NavRowCosts, NavSectionShape } from './nav-height-budget';