@boost33/ui 0.7.1 → 0.8.0-next.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.
- package/CHANGELOG.md +183 -0
- package/dist/buttons.module.css.js +10 -0
- package/dist/buttons.module.css.js.map +1 -0
- package/dist/components/AppShell/AppShell.d.ts +85 -17
- package/dist/components/AppShell/AppShell.js +75 -50
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/AppShell/AppShell.module.css.js +22 -16
- package/dist/components/AppShell/AppShell.module.css.js.map +1 -1
- package/dist/components/AppSwitcher/AppSwitcher.d.ts +90 -45
- package/dist/components/AppSwitcher/AppSwitcher.js +71 -66
- package/dist/components/AppSwitcher/AppSwitcher.js.map +1 -1
- package/dist/components/AppSwitcher/AppSwitcher.module.css.js +42 -36
- package/dist/components/AppSwitcher/AppSwitcher.module.css.js.map +1 -1
- package/dist/components/AppSwitcher/icons.js +10 -10
- package/dist/components/AppSwitcher/icons.js.map +1 -1
- package/dist/components/AppSwitcher/labels.d.ts +11 -22
- package/dist/components/AppSwitcher/labels.js.map +1 -1
- package/dist/components/CommandPalette/CommandPalette.d.ts +8 -2
- package/dist/components/CommandPalette/CommandPalette.js.map +1 -1
- package/dist/components/NavItem/NavItem.d.ts +15 -2
- package/dist/components/NavItem/NavItem.js +14 -11
- package/dist/components/NavItem/NavItem.js.map +1 -1
- package/dist/components/NavItem/NavItem.module.css.js +6 -2
- package/dist/components/NavItem/NavItem.module.css.js.map +1 -1
- package/dist/components/NavSection/NavSection.module.css.js +5 -5
- package/dist/components/NotificationButton/NotificationButton.d.ts +29 -0
- package/dist/components/NotificationButton/NotificationButton.js +23 -0
- package/dist/components/NotificationButton/NotificationButton.js.map +1 -0
- package/dist/components/NotificationButton/NotificationButton.module.css.js +14 -0
- package/dist/components/NotificationButton/NotificationButton.module.css.js.map +1 -0
- package/dist/components/NotificationButton/labels.d.ts +12 -0
- package/dist/components/NotificationButton/labels.js +15 -0
- package/dist/components/NotificationButton/labels.js.map +1 -0
- package/dist/components/NotificationCenter/NotificationCenter.js +37 -36
- package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.module.css.js +43 -43
- package/dist/components/Sidebar/NavHeightBudgetWarning.js +2 -2
- package/dist/components/Sidebar/NavHeightBudgetWarning.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.module.css.js +1 -1
- package/dist/components/TopBar/TopBar.d.ts +54 -4
- package/dist/components/TopBar/TopBar.js +22 -7
- package/dist/components/TopBar/TopBar.js.map +1 -1
- package/dist/components/TopBar/TopBar.module.css.js +12 -8
- package/dist/components/TopBar/TopBar.module.css.js.map +1 -1
- package/dist/components/TopBar/labels.d.ts +20 -0
- package/dist/components/TopBar/labels.js +15 -0
- package/dist/components/TopBar/labels.js.map +1 -0
- package/dist/components/WalletChip/WalletChip.d.ts +77 -2
- package/dist/components/WalletChip/WalletChip.js +142 -50
- package/dist/components/WalletChip/WalletChip.js.map +1 -1
- package/dist/components/WalletChip/WalletChip.module.css.js +93 -47
- package/dist/components/WalletChip/WalletChip.module.css.js.map +1 -1
- package/dist/components/WalletChip/labels.d.ts +63 -1
- package/dist/components/WalletChip/labels.js +21 -6
- package/dist/components/WalletChip/labels.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.js +55 -49
- package/dist/index.js.map +1 -1
- package/dist/nav-height-budget.d.ts +12 -9
- package/dist/nav-height-budget.js +3 -3
- package/dist/nav-height-budget.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/theme.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationCenter.js","sources":["../../../src/components/NotificationCenter/NotificationCenter.tsx"],"sourcesContent":["// Not a client module, and that is a statement about this stage rather than\n// about the component. Stage A holds no state, wires no handler and reads no\n// clock, so there is nothing here a Server Component cannot carry. Stage B adds\n// `useAnchoredOverlay` and the directive with it -- see the note on the panel\n// below, and tests/guards.test.ts, which pins the exact set of built modules\n// carrying \"use client\" so the directive cannot appear here unnoticed.\n\nimport { useId, type ElementType } from 'react'\nimport { ProductMark } from '../ProductMark/ProductMark'\nimport { EmptyBell, SourceBellMark } from './icons'\nimport { resolveNotificationCenterLabels, type NotificationCenterLabels } from './labels'\nimport styles from './NotificationCenter.module.css'\n\n/**\n * The size the source mark renders at, in px.\n *\n * 22, which is `--spacing-5_5` written as a number because `ProductMark.size` is\n * a React prop and cannot read a custom property. ProductMark's own default is\n * 20 and the AppSwitcher's rows deliberately take it -- that is the Wallet's\n * size at the Wallet's call sites, settled against three approved baselines, and\n * its comment warns against passing a size at all. This is the other case the\n * comment contemplates and not a contradiction of it: there is no Wallet\n * rendering of this panel and no approved baseline to move, so 22 is the drawing\n * on page 004 reproduced rather than a settled measurement overruled. The person\n * at the fidelity gate settles it (CLAUDE.md, \"The three components the Wallet\n * never rendered\"), and until then nothing here is authoritative.\n */\nconst SOURCE_MARK_PX = 22\n\n/**\n * One notification, as the host supplies it. **This package invents no backend\n * and fetches nothing** -- the array is the host's, and so is every value in a\n * row (B33-494's data decision, 2026-10-06; CLAUDE.md, \"it renders what it is\n * given\").\n *\n * The shape is deliberately generic. Nothing in it names a lead, a campaign or a\n * prospect, and nothing in it should: a field that carried product meaning is\n * how a shared component turns back into four private ones (CLAUDE.md). A\n * notification here is a title, where it came from, when, whether it has been\n * read, and optionally somewhere to go.\n */\nexport interface NotificationCenterItem {\n /** Stable identity for the row, the host's. Used as the React key, never rendered. */\n id: string\n /** What happened, in the host's words. The row's first line. */\n title: string\n /**\n * Where it came from, in the host's words -- a product's display name, or\n * something that is not a product at all (\"Account\"). The first half of the\n * row's second line.\n *\n * A display string and not an identifier: `slug` below is what selects the\n * artwork, and the two are separate for the same reason `AppSwitcherProduct`\n * keeps `slug` and `name` apart. Identity owns names, and this package does\n * not translate them.\n */\n source: string\n /**\n * When it happened, **already formatted by the host** -- \"12 minutes ago\",\n * \"Yesterday\", \"3 Oct\". The second half of the row's second line.\n *\n * **This package does not format it, and the choice is deliberate.** The chip\n * formats its figures from a `locale` prop (B33-876), so the obvious move is\n * to do the same here and take a `Date`. Three things separate the two.\n *\n * A number is a pure function of its value and a locale; a *relative* time is\n * not. It needs a `now`, so the component would have to read a clock, and then\n * re-read it on a timer or go stale on screen -- which is state, in a\n * component whose whole contract is that it has none.\n *\n * It would also make this component's own fidelity gate unreproducible. The\n * baseline for an unbuilt component is captured from the specimen route by the\n * person approving it (CLAUDE.md, tests/baselines/README.md \"Designed\n * baselines\"), and a panel that renders \"12 minutes ago\" from `Date.now()`\n * renders a different string an hour later, so the diff would fail for reasons\n * no change caused. `4,600` is `4,600` forever; \"12 minutes ago\" is not.\n *\n * And the hard part is editorial rather than linguistic. Where a product cuts\n * over from \"12 minutes ago\" to \"Yesterday\" to a date is a policy each product\n * is entitled to differ on, and `Intl.RelativeTimeFormat` does not decide it\n * -- a caller has to pick the value and the unit before it can be called. The\n * host already holds the raw timestamp under B33-494's decision; it is the\n * side that can answer.\n *\n * `dateTime` below is how the machine-readable value still reaches the page.\n */\n time: string\n /**\n * The machine-readable timestamp for the `<time>` element's `datetime`\n * attribute -- an ISO 8601 string. Optional: supply it and the row's time is a\n * `<time dateTime>`; omit it and the same text renders in a plain `<span>`.\n *\n * This is what `time` being preformatted costs and how the cost is paid back.\n * A relative phrase on its own tells assistive technology and anything else\n * reading the DOM nothing about *when*; the attribute carries the instant\n * without this package deciding how to say it.\n */\n dateTime?: string\n /**\n * Identity's product slug, selecting the ProductMark artwork for the row's\n * tile. Optional, because a notification need not come from a product -- an\n * account or security event does not, and the drawing's own fifth row is one.\n * Omit it and the row draws the panel's bell instead (see `SourceBellMark`).\n *\n * A slug this package has no artwork for degrades to the bare tile rather than\n * throwing, exactly as it does in the switcher (ADR-0003).\n */\n slug?: string\n /**\n * Where selecting this notification goes. Optional: a row with no `href` is\n * not a link and is drawn as plain content, which is the honest rendering for\n * something there is nothing to open for.\n *\n * A row that does navigate renders through `linkComponent` (ADR-0011).\n */\n href?: string\n /**\n * Whether this notification is still unread. Defaults to `false`.\n *\n * **The flag is spelled `unread` and defaults to not-lit on purpose**, which\n * is the same decision the TopBar's bell already carries and for the same\n * reason B33-494 records: a permanently lit marker on a panel nobody has\n * cleared trains people to ignore it, and that attention does not come back.\n * A host that does not track read state passes nothing and gets a quiet panel;\n * a host that does says so row by row.\n */\n unread?: boolean\n}\n\nexport interface NotificationCenterProps {\n /**\n * The notifications, in the order the host wants them read. Rendered whole --\n * this component does not sort, filter, page or truncate, because every one of\n * those is a decision about data it does not own.\n *\n * An empty array is not an error state. It is the empty rendering, and it is\n * the state most consumers see first: a host with no feed wired yet has\n * nothing to pass (B33-494).\n */\n items: NotificationCenterItem[]\n /**\n * Where the footer's \"View all notifications\" link goes. Omit it and the\n * footer is not rendered -- a link to a page the host does not have is worse\n * than no link.\n *\n * The empty rendering never draws the footer, whether this is supplied or not:\n * there is nothing to see more of.\n */\n viewAllHref?: string\n /**\n * Every user-facing string that is not row data. Each field defaults to\n * English, and a field left out -- or supplied as `undefined`, which is what a\n * missed next-intl key looks like -- keeps its default (B33-873).\n */\n labels?: Partial<NotificationCenterLabels>\n /**\n * The element a row with an `href` renders through, and the footer link with\n * it. Defaults to a plain `a`; a router-owning consumer passes its own so the\n * rows route client-side with the locale prefix intact (ADR-0011, B33-847).\n */\n linkComponent?: ElementType\n}\n\n/**\n * The panel behind the top bar's bell: everything the four products did, in one\n * list, with the unread ones marked (B33-494).\n *\n * **It is presentational and it owns no feed.** The notifications arrive as a\n * prop and this package neither fetches, stores nor subscribes to anything --\n * there is no cross-product event source in any repo, and a component that\n * implied one would make the shared shell the place four services integrate,\n * which is the opposite of the dependency direction every other component here\n * has. That was decided on the ticket on 2026-10-06; the feed is separate work,\n * outside 1.0.\n *\n * **This is Stage A: static.** The panel renders, the tabs and the header action\n * draw, and nothing responds to anything (CLAUDE.md, \"Presentational-first\").\n * There is no open state here because there is no trigger here either: the bell\n * is the TopBar's, and connecting the two -- `useAnchoredOverlay` for the shared\n * dismissal contract (ADR-0009), the bell's `aria-haspopup`/`aria-expanded`, the\n * filter the tabs select, the mark-all handler -- is Stage B, after a human has\n * approved what this draws.\n *\n * **A disclosure, not a menu** (ADR-0010): the panel carries no widget role, the\n * tabs are not an ARIA `tablist`, the rows are the real links they already are,\n * and Tab is the document's own order, so the header button, both filters, every\n * row and the footer link are each their own tab stop. An ARIA tab set or a\n * `menu` would promise a composite widget with one tab stop and arrow-key\n * traversal, which is the promise ADR-0010 took the switcher out of.\n */\nexport function NotificationCenter({\n items,\n viewAllHref,\n labels,\n linkComponent,\n}: NotificationCenterProps) {\n const t = resolveNotificationCenterLabels(labels)\n const Link: ElementType = linkComponent ?? 'a'\n const headingId = useId()\n const unreadCount = items.filter((item) => item.unread === true).length\n const isEmpty = items.length === 0\n\n return (\n // Named by the heading it already draws rather than by a second copy of the\n // same word in an aria-label (ADR-0010; see `title` in labels.ts).\n <div className={styles.panel} aria-labelledby={headingId}>\n <div className={styles.header}>\n <h2 className={styles.title} id={headingId}>\n {t.title}\n </h2>\n {/* Nothing to mark, and nothing to mark it on: the empty rendering drops\n the action rather than offering a control that would do nothing even\n once it is wired. */}\n {!isEmpty && (\n <button type=\"button\" className={styles.markAll}>\n {t.markAllAsRead}\n </button>\n )}\n </div>\n\n {isEmpty ? (\n <div className={styles.empty}>\n <EmptyBell />\n <div className={styles.emptyText}>\n <p className={styles.emptyTitle}>{t.emptyTitle}</p>\n <p className={styles.emptyBody}>{t.emptyBody}</p>\n </div>\n </div>\n ) : (\n <>\n {/* Two toggle buttons, not a `role=\"tablist\"`. The ARIA tabs pattern\n requires one tab stop for the set and arrow keys to move between\n them, which contradicts ADR-0010's \"Tab is the document's tab\n order, unchanged and unmanaged\" -- and the APG's own banner, \"No\n ARIA is better than Bad ARIA\". `aria-pressed` says which filter is\n on without claiming a composite widget.\n\n Both are pressed-state-only in Stage A: \"All\" is on because the\n list under it is every row. Which filter is selected becomes state\n when the filtering is wired, and the markup does not change when it\n does. */}\n <div className={styles.tabs}>\n <button type=\"button\" className={styles.tab} aria-pressed=\"true\">\n {t.all}\n </button>\n <button type=\"button\" className={styles.tab} aria-pressed=\"false\">\n {t.unread}\n {/* Counted off `items`, never passed in, so it cannot disagree\n with the list beneath it. Drawn only when there is something to\n count -- a \"0\" pill is a decoration that reads as a state. */}\n {unreadCount > 0 && <span className={styles.count}>{unreadCount}</span>}\n </button>\n </div>\n\n <ul className={styles.list}>\n {items.map((item) => {\n const unread = item.unread === true\n // A row that navigates is a link; a row with nowhere to go is not\n // dressed as one. `linkComponent` is only consulted for the linked\n // form, exactly as ADR-0011 has NavItem consult it.\n const Row: ElementType = item.href != null ? Link : 'div'\n\n return (\n <li key={item.id}>\n <Row\n className={styles.row}\n href={item.href}\n data-unread={unread ? 'true' : undefined}\n >\n {item.slug != null ? (\n <ProductMark slug={item.slug} size={SOURCE_MARK_PX} className={styles.sourceMark} />\n ) : (\n <SourceBellMark size={SOURCE_MARK_PX} />\n )}\n <span className={styles.rowText}>\n <span className={styles.rowTitle}>{item.title}</span>\n <span className={styles.rowMeta}>\n {/* The whole second line through one label, so a locale\n owns both the word order and the separator. The\n machine-readable instant rides on the <time> element\n when the host supplies one; the text is the host's\n either way. */}\n {item.dateTime != null ? (\n <time dateTime={item.dateTime}>{t.meta(item.source, item.time)}</time>\n ) : (\n t.meta(item.source, item.time)\n )}\n </span>\n </span>\n {/* A fixed lane, drawn on every row whether or not it holds\n a dot, so the dots line up in one column and a read row\n does not reflow its text wider than an unread one. */}\n <span className={styles.unreadSlot}>\n {unread && (\n <>\n <span className={styles.dot} />\n {/* The dot is a colour and nothing else, which reaches\n nobody on a screen reader. This is the same state\n in words (CLAUDE.md, \"Accessibility is part of\n done\"). */}\n <span className={styles.srOnly}>{t.unreadItem}</span>\n </>\n )}\n </span>\n </Row>\n </li>\n )\n })}\n </ul>\n\n {viewAllHref != null && (\n <div className={styles.footer}>\n <Link className={styles.viewAll} href={viewAllHref}>\n {t.viewAll}\n </Link>\n </div>\n )}\n </>\n )}\n </div>\n )\n}\n"],"names":["SOURCE_MARK_PX","NotificationCenter","items","viewAllHref","labels","linkComponent","t","resolveNotificationCenterLabels","Link","headingId","useId","unreadCount","item","isEmpty","styles","jsxs","jsx","EmptyBell","Fragment","unread","Row","ProductMark","SourceBellMark"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NotificationCenter.js","sources":["../../../src/components/NotificationCenter/NotificationCenter.tsx"],"sourcesContent":["// Not a client module, and that is a statement about this stage rather than\n// about the component. Stage A holds no state, wires no handler and reads no\n// clock, so there is nothing here a Server Component cannot carry. Stage B adds\n// `useAnchoredOverlay` and the directive with it -- see the note on the panel\n// below, and tests/guards.test.ts, which pins the exact set of built modules\n// carrying \"use client\" so the directive cannot appear here unnoticed.\n\nimport { useId, type ElementType } from 'react'\nimport { ProductMark } from '../ProductMark/ProductMark'\nimport { EmptyBell, SourceBellMark } from './icons'\nimport { resolveNotificationCenterLabels, type NotificationCenterLabels } from './labels'\nimport buttons from '../../buttons.module.css'\nimport styles from './NotificationCenter.module.css'\n\n/**\n * The size the source mark renders at, in px.\n *\n * 22, which is `--spacing-5_5` written as a number because `ProductMark.size` is\n * a React prop and cannot read a custom property. ProductMark's own default is\n * 20 and the AppSwitcher's rows deliberately take it -- that is the Wallet's\n * size at the Wallet's call sites, settled against three approved baselines, and\n * its comment warns against passing a size at all. This is the other case the\n * comment contemplates and not a contradiction of it: there is no Wallet\n * rendering of this panel and no approved baseline to move, so 22 is the drawing\n * on page 004 reproduced rather than a settled measurement overruled. The person\n * at the fidelity gate settles it (CLAUDE.md, \"The three components the Wallet\n * never rendered\"), and until then nothing here is authoritative.\n */\nconst SOURCE_MARK_PX = 22\n\n/**\n * One notification, as the host supplies it. **This package invents no backend\n * and fetches nothing** -- the array is the host's, and so is every value in a\n * row (B33-494's data decision, 2026-10-06; CLAUDE.md, \"it renders what it is\n * given\").\n *\n * The shape is deliberately generic. Nothing in it names a lead, a campaign or a\n * prospect, and nothing in it should: a field that carried product meaning is\n * how a shared component turns back into four private ones (CLAUDE.md). A\n * notification here is a title, where it came from, when, whether it has been\n * read, and optionally somewhere to go.\n */\nexport interface NotificationCenterItem {\n /** Stable identity for the row, the host's. Used as the React key, never rendered. */\n id: string\n /** What happened, in the host's words. The row's first line. */\n title: string\n /**\n * Where it came from, in the host's words -- a product's display name, or\n * something that is not a product at all (\"Account\"). The first half of the\n * row's second line.\n *\n * A display string and not an identifier: `slug` below is what selects the\n * artwork, and the two are separate for the same reason `AppSwitcherProduct`\n * keeps `slug` and `name` apart. Identity owns names, and this package does\n * not translate them.\n */\n source: string\n /**\n * When it happened, **already formatted by the host** -- \"12 minutes ago\",\n * \"Yesterday\", \"3 Oct\". The second half of the row's second line.\n *\n * **This package does not format it, and the choice is deliberate.** The chip\n * formats its figures from a `locale` prop (B33-876), so the obvious move is\n * to do the same here and take a `Date`. Three things separate the two.\n *\n * A number is a pure function of its value and a locale; a *relative* time is\n * not. It needs a `now`, so the component would have to read a clock, and then\n * re-read it on a timer or go stale on screen -- which is state, in a\n * component whose whole contract is that it has none.\n *\n * It would also make this component's own fidelity gate unreproducible. The\n * baseline for an unbuilt component is captured from the specimen route by the\n * person approving it (CLAUDE.md, tests/baselines/README.md \"Designed\n * baselines\"), and a panel that renders \"12 minutes ago\" from `Date.now()`\n * renders a different string an hour later, so the diff would fail for reasons\n * no change caused. `4,600` is `4,600` forever; \"12 minutes ago\" is not.\n *\n * And the hard part is editorial rather than linguistic. Where a product cuts\n * over from \"12 minutes ago\" to \"Yesterday\" to a date is a policy each product\n * is entitled to differ on, and `Intl.RelativeTimeFormat` does not decide it\n * -- a caller has to pick the value and the unit before it can be called. The\n * host already holds the raw timestamp under B33-494's decision; it is the\n * side that can answer.\n *\n * `dateTime` below is how the machine-readable value still reaches the page.\n */\n time: string\n /**\n * The machine-readable timestamp for the `<time>` element's `datetime`\n * attribute -- an ISO 8601 string. Optional: supply it and the row's time is a\n * `<time dateTime>`; omit it and the same text renders in a plain `<span>`.\n *\n * This is what `time` being preformatted costs and how the cost is paid back.\n * A relative phrase on its own tells assistive technology and anything else\n * reading the DOM nothing about *when*; the attribute carries the instant\n * without this package deciding how to say it.\n */\n dateTime?: string\n /**\n * Identity's product slug, selecting the ProductMark artwork for the row's\n * tile. Optional, because a notification need not come from a product -- an\n * account or security event does not, and the drawing's own fifth row is one.\n * Omit it and the row draws the panel's bell instead (see `SourceBellMark`).\n *\n * A slug this package has no artwork for degrades to the bare tile rather than\n * throwing, exactly as it does in the switcher (ADR-0003).\n */\n slug?: string\n /**\n * Where selecting this notification goes. Optional: a row with no `href` is\n * not a link and is drawn as plain content, which is the honest rendering for\n * something there is nothing to open for.\n *\n * A row that does navigate renders through `linkComponent` (ADR-0011).\n */\n href?: string\n /**\n * Whether this notification is still unread. Defaults to `false`.\n *\n * **The flag is spelled `unread` and defaults to not-lit on purpose**, which\n * is the same decision the TopBar's bell already carries and for the same\n * reason B33-494 records: a permanently lit marker on a panel nobody has\n * cleared trains people to ignore it, and that attention does not come back.\n * A host that does not track read state passes nothing and gets a quiet panel;\n * a host that does says so row by row.\n */\n unread?: boolean\n}\n\nexport interface NotificationCenterProps {\n /**\n * The notifications, in the order the host wants them read. Rendered whole --\n * this component does not sort, filter, page or truncate, because every one of\n * those is a decision about data it does not own.\n *\n * An empty array is not an error state. It is the empty rendering, and it is\n * the state most consumers see first: a host with no feed wired yet has\n * nothing to pass (B33-494).\n */\n items: NotificationCenterItem[]\n /**\n * Where the footer's \"View all notifications\" link goes. Omit it and the\n * footer is not rendered -- a link to a page the host does not have is worse\n * than no link.\n *\n * The empty rendering never draws the footer, whether this is supplied or not:\n * there is nothing to see more of.\n */\n viewAllHref?: string\n /**\n * Every user-facing string that is not row data. Each field defaults to\n * English, and a field left out -- or supplied as `undefined`, which is what a\n * missed next-intl key looks like -- keeps its default (B33-873).\n */\n labels?: Partial<NotificationCenterLabels>\n /**\n * The element a row with an `href` renders through, and the footer link with\n * it. Defaults to a plain `a`; a router-owning consumer passes its own so the\n * rows route client-side with the locale prefix intact (ADR-0011, B33-847).\n */\n linkComponent?: ElementType\n}\n\n/**\n * The panel behind the top bar's bell: everything the four products did, in one\n * list, with the unread ones marked (B33-494).\n *\n * **It is presentational and it owns no feed.** The notifications arrive as a\n * prop and this package neither fetches, stores nor subscribes to anything --\n * there is no cross-product event source in any repo, and a component that\n * implied one would make the shared shell the place four services integrate,\n * which is the opposite of the dependency direction every other component here\n * has. That was decided on the ticket on 2026-10-06; the feed is separate work,\n * outside 1.0.\n *\n * **This is Stage A: static.** The panel renders, the tabs and the header action\n * draw, and nothing responds to anything (CLAUDE.md, \"Presentational-first\").\n * There is no open state here because there is no trigger here either: the bell\n * is the TopBar's, and connecting the two -- `useAnchoredOverlay` for the shared\n * dismissal contract (ADR-0009), the bell's `aria-haspopup`/`aria-expanded`, the\n * filter the tabs select, the mark-all handler -- is Stage B, after a human has\n * approved what this draws.\n *\n * **A disclosure, not a menu** (ADR-0010): the panel carries no widget role, the\n * tabs are not an ARIA `tablist`, the rows are the real links they already are,\n * and Tab is the document's own order, so the header button, both filters, every\n * row and the footer link are each their own tab stop. An ARIA tab set or a\n * `menu` would promise a composite widget with one tab stop and arrow-key\n * traversal, which is the promise ADR-0010 took the switcher out of.\n */\nexport function NotificationCenter({\n items,\n viewAllHref,\n labels,\n linkComponent,\n}: NotificationCenterProps) {\n const t = resolveNotificationCenterLabels(labels)\n const Link: ElementType = linkComponent ?? 'a'\n const headingId = useId()\n const unreadCount = items.filter((item) => item.unread === true).length\n const isEmpty = items.length === 0\n\n return (\n // Named by the heading it already draws rather than by a second copy of the\n // same word in an aria-label (ADR-0010; see `title` in labels.ts).\n <div className={styles.panel} aria-labelledby={headingId}>\n <div className={styles.header}>\n <h2 className={styles.title} id={headingId}>\n {t.title}\n </h2>\n {/* Nothing to mark, and nothing to mark it on: the empty rendering drops\n the action rather than offering a control that would do nothing even\n once it is wired. */}\n {!isEmpty && (\n <button type=\"button\" className={`${buttons.quiet} ${styles.markAll}`}>\n {t.markAllAsRead}\n </button>\n )}\n </div>\n\n {isEmpty ? (\n <div className={styles.empty}>\n <EmptyBell />\n <div className={styles.emptyText}>\n <p className={styles.emptyTitle}>{t.emptyTitle}</p>\n <p className={styles.emptyBody}>{t.emptyBody}</p>\n </div>\n </div>\n ) : (\n <>\n {/* Two toggle buttons, not a `role=\"tablist\"`. The ARIA tabs pattern\n requires one tab stop for the set and arrow keys to move between\n them, which contradicts ADR-0010's \"Tab is the document's tab\n order, unchanged and unmanaged\" -- and the APG's own banner, \"No\n ARIA is better than Bad ARIA\". `aria-pressed` says which filter is\n on without claiming a composite widget.\n\n Both are pressed-state-only in Stage A: \"All\" is on because the\n list under it is every row. Which filter is selected becomes state\n when the filtering is wired, and the markup does not change when it\n does. */}\n <div className={styles.tabs}>\n <button type=\"button\" className={styles.tab} aria-pressed=\"true\">\n {t.all}\n </button>\n <button type=\"button\" className={styles.tab} aria-pressed=\"false\">\n {t.unread}\n {/* Counted off `items`, never passed in, so it cannot disagree\n with the list beneath it. Drawn only when there is something to\n count -- a \"0\" pill is a decoration that reads as a state. */}\n {unreadCount > 0 && <span className={styles.count}>{unreadCount}</span>}\n </button>\n </div>\n\n <ul className={styles.list}>\n {items.map((item) => {\n const unread = item.unread === true\n // A row that navigates is a link; a row with nowhere to go is not\n // dressed as one. `linkComponent` is only consulted for the linked\n // form, exactly as ADR-0011 has NavItem consult it.\n const Row: ElementType = item.href != null ? Link : 'div'\n\n return (\n <li key={item.id}>\n <Row\n className={styles.row}\n href={item.href}\n data-unread={unread ? 'true' : undefined}\n >\n {item.slug != null ? (\n <ProductMark slug={item.slug} size={SOURCE_MARK_PX} className={styles.sourceMark} />\n ) : (\n <SourceBellMark size={SOURCE_MARK_PX} />\n )}\n <span className={styles.rowText}>\n <span className={styles.rowTitle}>{item.title}</span>\n <span className={styles.rowMeta}>\n {/* The whole second line through one label, so a locale\n owns both the word order and the separator. The\n machine-readable instant rides on the <time> element\n when the host supplies one; the text is the host's\n either way. */}\n {item.dateTime != null ? (\n <time dateTime={item.dateTime}>{t.meta(item.source, item.time)}</time>\n ) : (\n t.meta(item.source, item.time)\n )}\n </span>\n </span>\n {/* A fixed lane, drawn on every row whether or not it holds\n a dot, so the dots line up in one column and a read row\n does not reflow its text wider than an unread one. */}\n <span className={styles.unreadSlot}>\n {unread && (\n <>\n <span className={styles.dot} />\n {/* The dot is a colour and nothing else, which reaches\n nobody on a screen reader. This is the same state\n in words (CLAUDE.md, \"Accessibility is part of\n done\"). */}\n <span className={styles.srOnly}>{t.unreadItem}</span>\n </>\n )}\n </span>\n </Row>\n </li>\n )\n })}\n </ul>\n\n {viewAllHref != null && (\n <div className={styles.footer}>\n <Link className={`${buttons.quiet} ${styles.viewAll}`} href={viewAllHref}>\n {t.viewAll}\n </Link>\n </div>\n )}\n </>\n )}\n </div>\n )\n}\n"],"names":["SOURCE_MARK_PX","NotificationCenter","items","viewAllHref","labels","linkComponent","t","resolveNotificationCenterLabels","Link","headingId","useId","unreadCount","item","isEmpty","styles","jsxs","jsx","buttons","EmptyBell","Fragment","unread","Row","ProductMark","SourceBellMark"],"mappings":";;;;;;;AA4BA,MAAMA,IAAiB;AAmKhB,SAASC,EAAmB;AAAA,EACjC,OAAAC;AAAA,EACA,aAAAC;AAAA,EACA,QAAAC;AAAA,EACA,eAAAC;AACF,GAA4B;AAC1B,QAAMC,IAAIC,EAAgCH,CAAM,GAC1CI,IAAoBH,KAAiB,KACrCI,IAAYC,EAAA,GACZC,IAAcT,EAAM,OAAO,CAACU,MAASA,EAAK,WAAW,EAAI,EAAE,QAC3DC,IAAUX,EAAM,WAAW;AAEjC;AAAA;AAAA;AAAA,sBAGG,OAAA,EAAI,WAAWY,EAAO,OAAO,mBAAiBL,GAC7C,UAAA;AAAA,MAAA,gBAAAM,EAAC,OAAA,EAAI,WAAWD,EAAO,QACrB,UAAA;AAAA,QAAA,gBAAAE,EAAC,QAAG,WAAWF,EAAO,OAAO,IAAIL,GAC9B,YAAE,MAAA,CACL;AAAA,QAIC,CAACI,KACA,gBAAAG,EAAC,UAAA,EAAO,MAAK,UAAS,WAAW,GAAGC,EAAQ,KAAK,IAAIH,EAAO,OAAO,IAChE,YAAE,cAAA,CACL;AAAA,MAAA,GAEJ;AAAA,MAECD,IACC,gBAAAE,EAAC,OAAA,EAAI,WAAWD,EAAO,OACrB,UAAA;AAAA,QAAA,gBAAAE,EAACE,GAAA,EAAU;AAAA,QACX,gBAAAH,EAAC,OAAA,EAAI,WAAWD,EAAO,WACrB,UAAA;AAAA,UAAA,gBAAAE,EAAC,KAAA,EAAE,WAAWF,EAAO,YAAa,YAAE,YAAW;AAAA,4BAC9C,KAAA,EAAE,WAAWA,EAAO,WAAY,YAAE,UAAA,CAAU;AAAA,QAAA,EAAA,CAC/C;AAAA,MAAA,EAAA,CACF,IAEA,gBAAAC,EAAAI,GAAA,EAYE,UAAA;AAAA,QAAA,gBAAAJ,EAAC,OAAA,EAAI,WAAWD,EAAO,MACrB,UAAA;AAAA,UAAA,gBAAAE,EAAC,UAAA,EAAO,MAAK,UAAS,WAAWF,EAAO,KAAK,gBAAa,QACvD,UAAAR,EAAE,IAAA,CACL;AAAA,UACA,gBAAAS,EAAC,YAAO,MAAK,UAAS,WAAWD,EAAO,KAAK,gBAAa,SACvD,UAAA;AAAA,YAAAR,EAAE;AAAA,YAIFK,IAAc,KAAK,gBAAAK,EAAC,UAAK,WAAWF,EAAO,OAAQ,UAAAH,EAAA,CAAY;AAAA,UAAA,EAAA,CAClE;AAAA,QAAA,GACF;AAAA,QAEA,gBAAAK,EAAC,QAAG,WAAWF,EAAO,MACnB,UAAAZ,EAAM,IAAI,CAACU,MAAS;AACnB,gBAAMQ,IAASR,EAAK,WAAW,IAIzBS,IAAmBT,EAAK,QAAQ,OAAOJ,IAAO;AAEpD,mCACG,MAAA,EACC,UAAA,gBAAAO;AAAA,YAACM;AAAA,YAAA;AAAA,cACC,WAAWP,EAAO;AAAA,cAClB,MAAMF,EAAK;AAAA,cACX,eAAaQ,IAAS,SAAS;AAAA,cAE9B,UAAA;AAAA,gBAAAR,EAAK,QAAQ,OACZ,gBAAAI,EAACM,GAAA,EAAY,MAAMV,EAAK,MAAM,MAAMZ,GAAgB,WAAWc,EAAO,YAAY,IAElF,gBAAAE,EAACO,GAAA,EAAe,MAAMvB,GAAgB;AAAA,gBAExC,gBAAAe,EAAC,QAAA,EAAK,WAAWD,EAAO,SACtB,UAAA;AAAA,kBAAA,gBAAAE,EAAC,QAAA,EAAK,WAAWF,EAAO,UAAW,YAAK,OAAM;AAAA,kBAC9C,gBAAAE,EAAC,QAAA,EAAK,WAAWF,EAAO,SAMrB,UAAAF,EAAK,YAAY,OAChB,gBAAAI,EAAC,QAAA,EAAK,UAAUJ,EAAK,UAAW,UAAAN,EAAE,KAAKM,EAAK,QAAQA,EAAK,IAAI,EAAA,CAAE,IAE/DN,EAAE,KAAKM,EAAK,QAAQA,EAAK,IAAI,EAAA,CAEjC;AAAA,gBAAA,GACF;AAAA,kCAIC,QAAA,EAAK,WAAWE,EAAO,YACrB,eACC,gBAAAC,EAAAI,GAAA,EACE,UAAA;AAAA,kBAAA,gBAAAH,EAAC,QAAA,EAAK,WAAWF,EAAO,IAAA,CAAK;AAAA,oCAK5B,QAAA,EAAK,WAAWA,EAAO,QAAS,YAAE,WAAA,CAAW;AAAA,gBAAA,EAAA,CAChD,EAAA,CAEJ;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA,EACF,GAzCOF,EAAK,EA0Cd;AAAA,QAEJ,CAAC,EAAA,CACH;AAAA,QAECT,KAAe,QACd,gBAAAa,EAAC,OAAA,EAAI,WAAWF,EAAO,QACrB,4BAACN,GAAA,EAAK,WAAW,GAAGS,EAAQ,KAAK,IAAIH,EAAO,OAAO,IAAI,MAAMX,GAC1D,UAAAG,EAAE,QAAA,CACL,EAAA,CACF;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA,EAAA,CAEJ;AAAA;AAEJ;"}
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
const t = "
|
|
1
|
+
const t = "_panel_1hhcn_36", _ = "_header_1hhcn_54", o = "_title_1hhcn_62", n = "_markAll_1hhcn_74", e = "_tab_1hhcn_79", c = "_tabs_1hhcn_88", h = "_count_1hhcn_123", r = "_list_1hhcn_133", s = "_row_1hhcn_148", l = "_sourceMark_1hhcn_179", a = "_sourceMarkGround_1hhcn_188", y = "_rowText_1hhcn_193", p = "_rowTitle_1hhcn_204", m = "_rowMeta_1hhcn_215", d = "_unreadSlot_1hhcn_226", u = "_dot_1hhcn_235", i = "_empty_1hhcn_250", w = "_emptyGlyph_1hhcn_259", T = "_emptyText_1hhcn_267", k = "_emptyTitle_1hhcn_274", M = "_emptyBody_1hhcn_282", x = "_footer_1hhcn_291", b = "_viewAll_1hhcn_300", A = "_srOnly_1hhcn_307", G = {
|
|
2
2
|
panel: t,
|
|
3
3
|
header: _,
|
|
4
|
-
title:
|
|
5
|
-
markAll:
|
|
6
|
-
tab:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
4
|
+
title: o,
|
|
5
|
+
markAll: n,
|
|
6
|
+
tab: e,
|
|
7
|
+
tabs: c,
|
|
8
|
+
count: h,
|
|
9
|
+
list: r,
|
|
10
|
+
row: s,
|
|
11
|
+
sourceMark: l,
|
|
12
|
+
sourceMarkGround: a,
|
|
13
|
+
rowText: y,
|
|
14
|
+
rowTitle: p,
|
|
15
|
+
rowMeta: m,
|
|
16
|
+
unreadSlot: d,
|
|
17
|
+
dot: u,
|
|
18
|
+
empty: i,
|
|
19
|
+
emptyGlyph: w,
|
|
20
|
+
emptyText: T,
|
|
21
|
+
emptyTitle: k,
|
|
22
|
+
emptyBody: M,
|
|
23
|
+
footer: x,
|
|
24
|
+
viewAll: b,
|
|
25
25
|
srOnly: A
|
|
26
26
|
};
|
|
27
27
|
export {
|
|
28
|
-
|
|
28
|
+
h as count,
|
|
29
29
|
G as default,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
u as dot,
|
|
31
|
+
i as empty,
|
|
32
|
+
M as emptyBody,
|
|
33
|
+
w as emptyGlyph,
|
|
34
|
+
T as emptyText,
|
|
35
|
+
k as emptyTitle,
|
|
36
|
+
x as footer,
|
|
37
37
|
_ as header,
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
r as list,
|
|
39
|
+
n as markAll,
|
|
40
40
|
t as panel,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
s as row,
|
|
42
|
+
m as rowMeta,
|
|
43
|
+
y as rowText,
|
|
44
|
+
p as rowTitle,
|
|
45
|
+
l as sourceMark,
|
|
46
|
+
a as sourceMarkGround,
|
|
47
47
|
A as srOnly,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
e as tab,
|
|
49
|
+
c as tabs,
|
|
50
|
+
o as title,
|
|
51
|
+
d as unreadSlot,
|
|
52
|
+
b as viewAll
|
|
53
53
|
};
|
|
54
54
|
//# sourceMappingURL=NotificationCenter.module.css.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as a } from "react/jsx-runtime";
|
|
3
3
|
import { useRef as s, useEffect as u } from "react";
|
|
4
|
-
import { checkNavHeightBudget as c, measureNavHeight as d, NAV_HEIGHT_BUDGET_PX as
|
|
4
|
+
import { checkNavHeightBudget as c, measureNavHeight as d, NAV_HEIGHT_BUDGET_PX as h } from "../../nav-height-budget.js";
|
|
5
5
|
function v({ label: r }) {
|
|
6
6
|
const o = s(null);
|
|
7
7
|
return u(() => {
|
|
@@ -18,7 +18,7 @@ function v({ label: r }) {
|
|
|
18
18
|
}
|
|
19
19
|
const e = c(d(n), n.clientHeight);
|
|
20
20
|
e.fits || console.warn(
|
|
21
|
-
`@boost33/ui Sidebar ("${r}"): the nav needs ${e.usedPx}px and the column has ${e.budgetPx}px for it, so it is ${e.overBy}px over and scrolling. Nothing is lost -- the
|
|
21
|
+
`@boost33/ui Sidebar ("${r}"): the nav needs ${e.usedPx}px and the column has ${e.budgetPx}px for it, so it is ${e.overBy}px over and scrolling. Nothing is lost -- the action and the footer stay pinned -- but the shell's nav budget is about ${h}px at a 900px viewport, and a nav past it is a nav worth cutting. navContentHeight() and navBudgetForViewport(), exported from this package, check a nav's shape before it renders.`
|
|
22
22
|
);
|
|
23
23
|
}), process.env.NODE_ENV === "production" ? null : /* @__PURE__ */ a("span", { ref: o, hidden: !0, "aria-hidden": "true" });
|
|
24
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavHeightBudgetWarning.js","sources":["../../../src/components/Sidebar/NavHeightBudgetWarning.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { NAV_HEIGHT_BUDGET_PX, checkNavHeightBudget, measureNavHeight } from '../../nav-height-budget'\n\n/*\n * The development-mode half of the nav budget, split out as the sidebar's one\n * client module.\n *\n * Sidebar.module.css stops an over-long nav from painting past the column, but\n * silently: the consumer sees a scrollbar and never learns their nav is too\n * long. The budget helpers in nav-height-budget.ts were written for exactly\n * that -- Boost Outbound's admin role has exceeded the budget once already --\n * and until this hook nothing in the shipped components ever called them, so a\n * consumer was protected only if they remembered to check by hand.\n *\n * It lives apart from Sidebar so Sidebar itself -- and the whole shell that\n * renders it -- can stay a Server Component (B33-846). A `console.warn` in\n * an effect is the only reason the sidebar column would otherwise be forced onto\n * the client, and a consumer should not ship the shell's static half to the\n * browser to get a development-only warning. So this is the boundary: it carries\n * \"use client\", and renders nothing a consumer sees. In production it is inert\n * and renders null; in development it renders one empty, hidden marker so the\n * effect has a handle on the sidebar element to measure from.\n *\n * `process.env.NODE_ENV` rather than an `import.meta` flag because every\n * bundler a consumer might use replaces that exact expression at build time\n * and drops the branch, which is how React itself ships its own dev warnings.\n *\n * Best effort by design: it re-measures on every render, after paint. Sidebar\n * re-renders when the consumer's stacked contents change, so a nav that grows\n * is re-checked; a nav that only overflows after a font swap or a window resize\n * is not caught, and does not need to be -- the case this exists for is a nav\n * that is too long for the column the moment it is rendered.\n */\nexport function NavHeightBudgetWarning({ label }: { label: string }) {\n // The marker sits as a direct child of the sidebar, so its parent is the\n // column and `:scope > nav` from there finds the same nav the CSS scroll\n // region rule selects.\n const marker = useRef<HTMLSpanElement>(null)\n\n useEffect(() => {\n if (process.env.NODE_ENV === 'production') return\n\n const element = marker.current?.parentElement\n if (!element) return\n\n const nav = element.querySelector(':scope > nav')\n if (!(nav instanceof HTMLElement)) {\n // Only worth saying when there is a nav and it is in the wrong place.\n // A sidebar that holds no nav at all is an ordinary thing to render.\n if (element.querySelector('nav')) {\n console.warn(\n `@boost33/ui Sidebar (\"${label}\"): the nav is nested inside another element, so it is not the sidebar's scroll ` +\n `region. Make the <nav> a direct child of the Sidebar, or a nav longer than the column will paint past its ` +\n `bottom edge instead of scrolling. See ADR-0005.`,\n )\n }\n return\n }\n\n const budget = checkNavHeightBudget(measureNavHeight(nav), nav.clientHeight)\n if (budget.fits) return\n\n console.warn(\n `@boost33/ui Sidebar (\"${label}\"): the nav needs ${budget.usedPx}px and the column has ${budget.budgetPx}px for it, ` +\n `so it is ${budget.overBy}px over and scrolling. Nothing is lost -- the
|
|
1
|
+
{"version":3,"file":"NavHeightBudgetWarning.js","sources":["../../../src/components/Sidebar/NavHeightBudgetWarning.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { NAV_HEIGHT_BUDGET_PX, checkNavHeightBudget, measureNavHeight } from '../../nav-height-budget'\n\n/*\n * The development-mode half of the nav budget, split out as the sidebar's one\n * client module.\n *\n * Sidebar.module.css stops an over-long nav from painting past the column, but\n * silently: the consumer sees a scrollbar and never learns their nav is too\n * long. The budget helpers in nav-height-budget.ts were written for exactly\n * that -- Boost Outbound's admin role has exceeded the budget once already --\n * and until this hook nothing in the shipped components ever called them, so a\n * consumer was protected only if they remembered to check by hand.\n *\n * It lives apart from Sidebar so Sidebar itself -- and the whole shell that\n * renders it -- can stay a Server Component (B33-846). A `console.warn` in\n * an effect is the only reason the sidebar column would otherwise be forced onto\n * the client, and a consumer should not ship the shell's static half to the\n * browser to get a development-only warning. So this is the boundary: it carries\n * \"use client\", and renders nothing a consumer sees. In production it is inert\n * and renders null; in development it renders one empty, hidden marker so the\n * effect has a handle on the sidebar element to measure from.\n *\n * `process.env.NODE_ENV` rather than an `import.meta` flag because every\n * bundler a consumer might use replaces that exact expression at build time\n * and drops the branch, which is how React itself ships its own dev warnings.\n *\n * Best effort by design: it re-measures on every render, after paint. Sidebar\n * re-renders when the consumer's stacked contents change, so a nav that grows\n * is re-checked; a nav that only overflows after a font swap or a window resize\n * is not caught, and does not need to be -- the case this exists for is a nav\n * that is too long for the column the moment it is rendered.\n */\nexport function NavHeightBudgetWarning({ label }: { label: string }) {\n // The marker sits as a direct child of the sidebar, so its parent is the\n // column and `:scope > nav` from there finds the same nav the CSS scroll\n // region rule selects.\n const marker = useRef<HTMLSpanElement>(null)\n\n useEffect(() => {\n if (process.env.NODE_ENV === 'production') return\n\n const element = marker.current?.parentElement\n if (!element) return\n\n const nav = element.querySelector(':scope > nav')\n if (!(nav instanceof HTMLElement)) {\n // Only worth saying when there is a nav and it is in the wrong place.\n // A sidebar that holds no nav at all is an ordinary thing to render.\n if (element.querySelector('nav')) {\n console.warn(\n `@boost33/ui Sidebar (\"${label}\"): the nav is nested inside another element, so it is not the sidebar's scroll ` +\n `region. Make the <nav> a direct child of the Sidebar, or a nav longer than the column will paint past its ` +\n `bottom edge instead of scrolling. See ADR-0005.`,\n )\n }\n return\n }\n\n const budget = checkNavHeightBudget(measureNavHeight(nav), nav.clientHeight)\n if (budget.fits) return\n\n console.warn(\n `@boost33/ui Sidebar (\"${label}\"): the nav needs ${budget.usedPx}px and the column has ${budget.budgetPx}px for it, ` +\n `so it is ${budget.overBy}px over and scrolling. Nothing is lost -- the action and the footer stay pinned -- but ` +\n `the shell's nav budget is about ${NAV_HEIGHT_BUDGET_PX}px at a 900px viewport, and a nav past it is a nav worth ` +\n `cutting. navContentHeight() and navBudgetForViewport(), exported from this package, check a nav's shape before ` +\n `it renders.`,\n )\n })\n\n if (process.env.NODE_ENV === 'production') return null\n return <span ref={marker} hidden aria-hidden=\"true\" />\n}\n"],"names":["NavHeightBudgetWarning","label","marker","useRef","useEffect","element","_a","nav","budget","checkNavHeightBudget","measureNavHeight","NAV_HEIGHT_BUDGET_PX"],"mappings":";;;;AAmCO,SAASA,EAAuB,EAAE,OAAAC,KAA4B;AAInE,QAAMC,IAASC,EAAwB,IAAI;AAkC3C,SAhCAC,EAAU,MAAM;;AACd,QAAI,QAAQ,IAAI,aAAa,aAAc;AAE3C,UAAMC,KAAUC,IAAAJ,EAAO,YAAP,gBAAAI,EAAgB;AAChC,QAAI,CAACD,EAAS;AAEd,UAAME,IAAMF,EAAQ,cAAc,cAAc;AAChD,QAAI,EAAEE,aAAe,cAAc;AAGjC,MAAIF,EAAQ,cAAc,KAAK,KAC7B,QAAQ;AAAA,QACN,yBAAyBJ,CAAK;AAAA,MAAA;AAKlC;AAAA,IACF;AAEA,UAAMO,IAASC,EAAqBC,EAAiBH,CAAG,GAAGA,EAAI,YAAY;AAC3E,IAAIC,EAAO,QAEX,QAAQ;AAAA,MACN,yBAAyBP,CAAK,qBAAqBO,EAAO,MAAM,yBAAyBA,EAAO,QAAQ,uBAC1FA,EAAO,MAAM,0HACUG,CAAoB;AAAA,IAAA;AAAA,EAI7D,CAAC,GAEG,QAAQ,IAAI,aAAa,eAAqB,yBAC1C,QAAA,EAAK,KAAKT,GAAQ,QAAM,IAAC,eAAY,QAAO;AACtD;"}
|
|
@@ -1,8 +1,58 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
+
import { TopBarLabels } from './labels';
|
|
2
3
|
export interface TopBarProps {
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The control that collapses the sidebar to its 56px rail and back, at the
|
|
6
|
+
* far left of the bar, before the mark (B33-1712). A slot: the product's own
|
|
7
|
+
* button, which owns the collapsed state.
|
|
8
|
+
*
|
|
9
|
+
* It has a slot of its own because the bar stopped drawing `breadcrumb` in
|
|
10
|
+
* 0.8.0, and a consumer that kept its toggle inside the breadcrumb -- the
|
|
11
|
+
* Wallet did -- would otherwise lose its only way into and out of the rail on
|
|
12
|
+
* upgrade, silently: the pixel diff passed the bar at 0.19% with the toggle
|
|
13
|
+
* gone. No board draws it; the rail keeps working with no new design.
|
|
14
|
+
*/
|
|
15
|
+
sidebarToggle?: ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* The left of the bar: the Boost33 mark and the organisation's pill, as the 3
|
|
18
|
+
* Nov 2026 redesign draws it (B33-1712, board 20). A slot. Inside `AppShell`
|
|
19
|
+
* the shell fills it from its own `mark`, `hubHref` and `org` props, so a
|
|
20
|
+
* consumer of the shell does not set this.
|
|
21
|
+
*/
|
|
22
|
+
start?: ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* The app switcher, at the centre of the bar after its "Switch Tools →"
|
|
25
|
+
* label. A slot: pass `AppSwitcher`. Omit it and neither it nor its label is
|
|
26
|
+
* drawn. Inside `AppShell`, the shell's `switcher` prop lands here.
|
|
27
|
+
*/
|
|
28
|
+
switcher?: ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* The token chip, after the switcher and its "Check Your Tokens →" label. A
|
|
31
|
+
* slot: pass `WalletChip` with `placement="topbar"`. Omit it and neither it
|
|
32
|
+
* nor its label is drawn. Inside `AppShell`, the shell's `wallet` prop lands
|
|
33
|
+
* here.
|
|
34
|
+
*/
|
|
35
|
+
tokens?: ReactNode;
|
|
36
|
+
/**
|
|
37
|
+
* The right of the bar: the notification button and the account avatar
|
|
38
|
+
* (board 20). A slot -- the product's own, or the package's
|
|
39
|
+
* `NotificationButton` beside the consumer's avatar.
|
|
40
|
+
*
|
|
41
|
+
* Search is not here and nothing replaces it: Jamal removed it on 3 Nov 2026
|
|
42
|
+
* until the feature is built (B33-1710), and the CommandPalette that would
|
|
43
|
+
* open from it is left out of 0.8.0 with no trigger and no shortcut.
|
|
44
|
+
*/
|
|
6
45
|
actions?: ReactNode;
|
|
46
|
+
/** The centre group's two labels. Each field defaults to English. */
|
|
47
|
+
labels?: Partial<TopBarLabels>;
|
|
48
|
+
/**
|
|
49
|
+
* Was the breadcrumb on the left of the bar. **Not drawn since 0.8.0**: the
|
|
50
|
+
* redesign puts the mark and the organisation there, and the page's own
|
|
51
|
+
* `PageHeader` names where you are. Optional so an existing call site still
|
|
52
|
+
* compiles.
|
|
53
|
+
*
|
|
54
|
+
* @deprecated Rendered nowhere since 0.8.0 (B33-1712). Stop passing it.
|
|
55
|
+
*/
|
|
56
|
+
breadcrumb?: ReactNode;
|
|
7
57
|
}
|
|
8
|
-
export declare function TopBar({
|
|
58
|
+
export declare function TopBar({ sidebarToggle, start, switcher, tokens, actions, labels }: TopBarProps): import("react").JSX.Element;
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { jsxs as e, Fragment as c, jsx as s } from "react/jsx-runtime";
|
|
2
|
+
import { resolveTopBarLabels as h } from "./labels.js";
|
|
3
|
+
import l from "./TopBar.module.css.js";
|
|
4
|
+
function N({ sidebarToggle: i, start: d, switcher: a, tokens: r, actions: o, labels: t }) {
|
|
5
|
+
const n = h(t), m = a != null || r != null;
|
|
6
|
+
return /* @__PURE__ */ e("div", { className: l.bar, children: [
|
|
7
|
+
/* @__PURE__ */ e("div", { className: l.start, children: [
|
|
8
|
+
i,
|
|
9
|
+
d
|
|
10
|
+
] }),
|
|
11
|
+
m && /* @__PURE__ */ e("div", { className: l.centre, children: [
|
|
12
|
+
a != null && /* @__PURE__ */ e(c, { children: [
|
|
13
|
+
/* @__PURE__ */ s("span", { className: l.label, children: n.switchTools }),
|
|
14
|
+
a
|
|
15
|
+
] }),
|
|
16
|
+
r != null && /* @__PURE__ */ e(c, { children: [
|
|
17
|
+
/* @__PURE__ */ s("span", { className: l.label, "data-after-switcher": a != null ? "true" : void 0, children: n.checkTokens }),
|
|
18
|
+
r
|
|
19
|
+
] })
|
|
20
|
+
] }),
|
|
21
|
+
/* @__PURE__ */ s("div", { className: l.end, children: o })
|
|
7
22
|
] });
|
|
8
23
|
}
|
|
9
24
|
export {
|
|
10
|
-
|
|
25
|
+
N as TopBar
|
|
11
26
|
};
|
|
12
27
|
//# sourceMappingURL=TopBar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TopBar.js","sources":["../../../src/components/TopBar/TopBar.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport styles from './TopBar.module.css'\n\nexport interface TopBarProps {\n
|
|
1
|
+
{"version":3,"file":"TopBar.js","sources":["../../../src/components/TopBar/TopBar.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { resolveTopBarLabels, type TopBarLabels } from './labels'\nimport styles from './TopBar.module.css'\n\nexport interface TopBarProps {\n /**\n * The control that collapses the sidebar to its 56px rail and back, at the\n * far left of the bar, before the mark (B33-1712). A slot: the product's own\n * button, which owns the collapsed state.\n *\n * It has a slot of its own because the bar stopped drawing `breadcrumb` in\n * 0.8.0, and a consumer that kept its toggle inside the breadcrumb -- the\n * Wallet did -- would otherwise lose its only way into and out of the rail on\n * upgrade, silently: the pixel diff passed the bar at 0.19% with the toggle\n * gone. No board draws it; the rail keeps working with no new design.\n */\n sidebarToggle?: ReactNode\n /**\n * The left of the bar: the Boost33 mark and the organisation's pill, as the 3\n * Nov 2026 redesign draws it (B33-1712, board 20). A slot. Inside `AppShell`\n * the shell fills it from its own `mark`, `hubHref` and `org` props, so a\n * consumer of the shell does not set this.\n */\n start?: ReactNode\n /**\n * The app switcher, at the centre of the bar after its \"Switch Tools →\"\n * label. A slot: pass `AppSwitcher`. Omit it and neither it nor its label is\n * drawn. Inside `AppShell`, the shell's `switcher` prop lands here.\n */\n switcher?: ReactNode\n /**\n * The token chip, after the switcher and its \"Check Your Tokens →\" label. A\n * slot: pass `WalletChip` with `placement=\"topbar\"`. Omit it and neither it\n * nor its label is drawn. Inside `AppShell`, the shell's `wallet` prop lands\n * here.\n */\n tokens?: ReactNode\n /**\n * The right of the bar: the notification button and the account avatar\n * (board 20). A slot -- the product's own, or the package's\n * `NotificationButton` beside the consumer's avatar.\n *\n * Search is not here and nothing replaces it: Jamal removed it on 3 Nov 2026\n * until the feature is built (B33-1710), and the CommandPalette that would\n * open from it is left out of 0.8.0 with no trigger and no shortcut.\n */\n actions?: ReactNode\n /** The centre group's two labels. Each field defaults to English. */\n labels?: Partial<TopBarLabels>\n /**\n * Was the breadcrumb on the left of the bar. **Not drawn since 0.8.0**: the\n * redesign puts the mark and the organisation there, and the page's own\n * `PageHeader` names where you are. Optional so an existing call site still\n * compiles.\n *\n * @deprecated Rendered nowhere since 0.8.0 (B33-1712). Stop passing it.\n */\n breadcrumb?: ReactNode\n}\n\n// The bar across the top of every screen (B33-1712). Three regions: the mark\n// and organisation on the left, the switcher and token chip in a centred group,\n// and the product's actions on the right. Left and right take equal shares of\n// what the centre leaves, which is what holds the centre group in the middle of\n// the bar whatever either side holds.\n//\n// A Server Component: no state, no handlers. The two controls in the centre are\n// client components passed in as slots.\nexport function TopBar({ sidebarToggle, start, switcher, tokens, actions, labels }: TopBarProps) {\n const t = resolveTopBarLabels(labels)\n const hasCentre = switcher != null || tokens != null\n\n return (\n <div className={styles.bar}>\n <div className={styles.start}>\n {sidebarToggle}\n {start}\n </div>\n {hasCentre && (\n <div className={styles.centre}>\n {switcher != null && (\n <>\n <span className={styles.label}>{t.switchTools}</span>\n {switcher}\n </>\n )}\n {tokens != null && (\n <>\n {/* The second label keeps 32px clear of the switcher, so each\n label reads as belonging to the control after it, not the one\n before it (board 20). With no switcher there is nothing to\n keep clear of, and the padding goes. */}\n <span className={styles.label} data-after-switcher={switcher != null ? 'true' : undefined}>\n {t.checkTokens}\n </span>\n {tokens}\n </>\n )}\n </div>\n )}\n <div className={styles.end}>{actions}</div>\n </div>\n )\n}\n"],"names":["TopBar","sidebarToggle","start","switcher","tokens","actions","labels","t","resolveTopBarLabels","hasCentre","jsxs","styles","Fragment","jsx"],"mappings":";;;AAoEO,SAASA,EAAO,EAAE,eAAAC,GAAe,OAAAC,GAAO,UAAAC,GAAU,QAAAC,GAAQ,SAAAC,GAAS,QAAAC,KAAuB;AAC/F,QAAMC,IAAIC,EAAoBF,CAAM,GAC9BG,IAAYN,KAAY,QAAQC,KAAU;AAEhD,SACE,gBAAAM,EAAC,OAAA,EAAI,WAAWC,EAAO,KACrB,UAAA;AAAA,IAAA,gBAAAD,EAAC,OAAA,EAAI,WAAWC,EAAO,OACpB,UAAA;AAAA,MAAAV;AAAA,MACAC;AAAA,IAAA,GACH;AAAA,IACCO,KACC,gBAAAC,EAAC,OAAA,EAAI,WAAWC,EAAO,QACpB,UAAA;AAAA,MAAAR,KAAY,QACX,gBAAAO,EAAAE,GAAA,EACE,UAAA;AAAA,QAAA,gBAAAC,EAAC,QAAA,EAAK,WAAWF,EAAO,OAAQ,YAAE,aAAY;AAAA,QAC7CR;AAAA,MAAA,GACH;AAAA,MAEDC,KAAU,QACT,gBAAAM,EAAAE,GAAA,EAKE,UAAA;AAAA,QAAA,gBAAAC,EAAC,QAAA,EAAK,WAAWF,EAAO,OAAO,uBAAqBR,KAAY,OAAO,SAAS,QAC7E,UAAAI,EAAE,YAAA,CACL;AAAA,QACCH;AAAA,MAAA,EAAA,CACH;AAAA,IAAA,GAEJ;AAAA,IAEF,gBAAAS,EAAC,OAAA,EAAI,WAAWF,EAAO,KAAM,UAAAN,EAAA,CAAQ;AAAA,EAAA,GACvC;AAEJ;"}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
const
|
|
2
|
-
bar:
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
const t = "_bar_1lxv8_9", e = "_start_1lxv8_25", _ = "_end_1lxv8_26", l = "_centre_1lxv8_45", n = "_label_1lxv8_54", s = {
|
|
2
|
+
bar: t,
|
|
3
|
+
start: e,
|
|
4
|
+
end: _,
|
|
5
|
+
centre: l,
|
|
6
|
+
label: n
|
|
5
7
|
};
|
|
6
8
|
export {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
t as bar,
|
|
10
|
+
l as centre,
|
|
11
|
+
s as default,
|
|
12
|
+
_ as end,
|
|
13
|
+
n as label,
|
|
14
|
+
e as start
|
|
11
15
|
};
|
|
12
16
|
//# sourceMappingURL=TopBar.module.css.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TopBar.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TopBar.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Every user-facing string the top bar renders itself. */
|
|
2
|
+
export interface TopBarLabels {
|
|
3
|
+
/**
|
|
4
|
+
* The text before the app switcher in the centre group (B33-1712, boards 18,
|
|
5
|
+
* 20 and 22 of the 3 Nov 2026 redesign). Text, not a control: it says what the
|
|
6
|
+
* control beside it is for. The arrow is part of the string, so a consumer
|
|
7
|
+
* writing right-to-left can turn it. Default "Switch Tools →".
|
|
8
|
+
*/
|
|
9
|
+
switchTools: string;
|
|
10
|
+
/** The text before the token chip, on the same terms. Default "Check Your Tokens →". */
|
|
11
|
+
checkTokens: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const defaultTopBarLabels: TopBarLabels;
|
|
14
|
+
/**
|
|
15
|
+
* Merge a consumer's partial `labels` onto the English defaults, treating an
|
|
16
|
+
* explicitly-`undefined` field as unsupplied, so a missed translation key falls
|
|
17
|
+
* back to English rather than blanking the label -- the same resolution
|
|
18
|
+
* `resolveAppSwitcherLabels` performs (B33-873).
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveTopBarLabels(labels?: Partial<TopBarLabels>): TopBarLabels;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const t = {
|
|
2
|
+
switchTools: "Switch Tools →",
|
|
3
|
+
checkTokens: "Check Your Tokens →"
|
|
4
|
+
};
|
|
5
|
+
function r(e) {
|
|
6
|
+
const o = Object.fromEntries(
|
|
7
|
+
Object.entries(e ?? {}).filter(([, s]) => s !== void 0)
|
|
8
|
+
);
|
|
9
|
+
return { ...t, ...o };
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
t as defaultTopBarLabels,
|
|
13
|
+
r as resolveTopBarLabels
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"labels.js","sources":["../../../src/components/TopBar/labels.ts"],"sourcesContent":["// The top bar's own user-facing words, each a prop with an English default --\n// the same pattern AppSwitcherLabels and WalletChipLabels follow, for the same\n// reason: the first consumers are French-default, and the package renders what\n// it is given rather than taking an i18n dependency (B33-849).\n\n/** Every user-facing string the top bar renders itself. */\nexport interface TopBarLabels {\n /**\n * The text before the app switcher in the centre group (B33-1712, boards 18,\n * 20 and 22 of the 3 Nov 2026 redesign). Text, not a control: it says what the\n * control beside it is for. The arrow is part of the string, so a consumer\n * writing right-to-left can turn it. Default \"Switch Tools →\".\n */\n switchTools: string\n /** The text before the token chip, on the same terms. Default \"Check Your Tokens →\". */\n checkTokens: string\n}\n\nexport const defaultTopBarLabels: TopBarLabels = {\n switchTools: 'Switch Tools →',\n checkTokens: 'Check Your Tokens →',\n}\n\n/**\n * Merge a consumer's partial `labels` onto the English defaults, treating an\n * explicitly-`undefined` field as unsupplied, so a missed translation key falls\n * back to English rather than blanking the label -- the same resolution\n * `resolveAppSwitcherLabels` performs (B33-873).\n */\nexport function resolveTopBarLabels(labels?: Partial<TopBarLabels>): TopBarLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<TopBarLabels>\n return { ...defaultTopBarLabels, ...supplied }\n}\n"],"names":["defaultTopBarLabels","resolveTopBarLabels","labels","supplied","value"],"mappings":"AAkBO,MAAMA,IAAoC;AAAA,EAC/C,aAAa;AAAA,EACb,aAAa;AACf;AAQO,SAASC,EAAoBC,GAA8C;AAChF,QAAMC,IAAW,OAAO;AAAA,IACtB,OAAO,QAAQD,KAAU,EAAE,EAAE,OAAO,CAAC,CAAA,EAAGE,CAAK,MAAMA,MAAU,MAAS;AAAA,EAAA;AAExE,SAAO,EAAE,GAAGJ,GAAqB,GAAGG,EAAA;AACtC;"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ElementType } from 'react';
|
|
1
2
|
import { WalletChipLabels } from './labels';
|
|
2
3
|
/**
|
|
3
4
|
* The props that say nothing about a balance, so both states take them
|
|
@@ -5,6 +6,31 @@ import { WalletChipLabels } from './labels';
|
|
|
5
6
|
* number formats it renders with.
|
|
6
7
|
*/
|
|
7
8
|
interface WalletChipChromeProps {
|
|
9
|
+
/**
|
|
10
|
+
* Where the chip is drawn. `'sidebar'`, the default, is the card it has always
|
|
11
|
+
* been, pinned at the sidebar's foot. `'topbar'` is the 3 Nov 2026 redesign's
|
|
12
|
+
* token chip (B33-1712, boards 20 and 21): a 32px pill in the top bar, drawn
|
|
13
|
+
* like the app switcher, that opens a popover with the breakdown.
|
|
14
|
+
*
|
|
15
|
+
* One component in two placements rather than a second component, because the
|
|
16
|
+
* figures, the bar's rules and every word are the same chip's (CLAUDE.md:
|
|
17
|
+
* build once, add a prop). `collapsed` applies to the sidebar placement only;
|
|
18
|
+
* the top bar has no rail.
|
|
19
|
+
*/
|
|
20
|
+
placement?: 'sidebar' | 'topbar';
|
|
21
|
+
/**
|
|
22
|
+
* Where the popover's "Open" link goes -- the Wallet. `placement="topbar"`
|
|
23
|
+
* only. Omit it and the popover draws no link. The consumer supplies the URL;
|
|
24
|
+
* Identity owns where the Wallet lives.
|
|
25
|
+
*/
|
|
26
|
+
walletHref?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The element the popover's "Open" link renders through, so it routes
|
|
29
|
+
* client-side with the locale prefix intact (ADR-0011). Defaults to `a`.
|
|
30
|
+
*/
|
|
31
|
+
linkComponent?: ElementType;
|
|
32
|
+
/** Render the top-bar popover open on first paint -- the specimen's open state. */
|
|
33
|
+
defaultOpen?: boolean;
|
|
8
34
|
/** The 56px rail. Abbreviates the balance and turns the split on its side. */
|
|
9
35
|
collapsed?: boolean;
|
|
10
36
|
/**
|
|
@@ -76,6 +102,8 @@ export interface WalletChipKnownProps extends WalletChipChromeProps {
|
|
|
76
102
|
* required here and forbidden in {@link WalletChipUnknownProps}.
|
|
77
103
|
*/
|
|
78
104
|
unknown?: false;
|
|
105
|
+
/** Absent or `false`: see {@link WalletChipTotalOnlyProps}. */
|
|
106
|
+
totalOnly?: false;
|
|
79
107
|
/** Remaining granted tokens. Granted burns first and expires. */
|
|
80
108
|
granted: number;
|
|
81
109
|
/** Remaining purchased tokens. Purchased never expires. */
|
|
@@ -156,6 +184,8 @@ export interface WalletChipKnownProps extends WalletChipChromeProps {
|
|
|
156
184
|
export interface WalletChipUnknownProps extends WalletChipChromeProps {
|
|
157
185
|
/** The balance could not be confirmed. Renders as unknown, never as zero. */
|
|
158
186
|
unknown: true;
|
|
187
|
+
totalOnly?: false;
|
|
188
|
+
balance?: never;
|
|
159
189
|
granted?: never;
|
|
160
190
|
purchased?: never;
|
|
161
191
|
grantCycle?: never;
|
|
@@ -178,6 +208,51 @@ export interface WalletChipUnknownProps extends WalletChipChromeProps {
|
|
|
178
208
|
* the members instead -- `Omit<WalletChipKnownProps, 'onTopUp'> |
|
|
179
209
|
* Omit<WalletChipUnknownProps, 'onTopUp'>` -- which is why both are exported.
|
|
180
210
|
*/
|
|
181
|
-
export type WalletChipProps = WalletChipKnownProps | WalletChipUnknownProps;
|
|
182
|
-
|
|
211
|
+
export type WalletChipProps = WalletChipKnownProps | WalletChipUnknownProps | WalletChipTotalOnlyProps;
|
|
212
|
+
/**
|
|
213
|
+
* The top-bar chip with a real total and no drawable split (B33-1712). Debits
|
|
214
|
+
* draw the grant first, then purchased, and may run a set share past zero (the
|
|
215
|
+
* Ledger's overdraft); credits add only to the bucket they name and never pay a
|
|
216
|
+
* negative one off. So the Ledger can report a bucket below zero -- granted 0 /
|
|
217
|
+
* purchased -40 (overdrawn, total -40), or granted 1000 / purchased -40 after
|
|
218
|
+
* the month's grant lands (total 960, for the rest of the month). A two-segment
|
|
219
|
+
* bar cannot draw a negative, and clamping it to zero would draw a split nobody
|
|
220
|
+
* has: a split invented by the interface. So this state takes **the total and
|
|
221
|
+
* no split**, and draws no bar and no granted / purchased legend.
|
|
222
|
+
*
|
|
223
|
+
* Below zero the figure is the distance past zero, positive, in --color-danger,
|
|
224
|
+
* with the words "past zero" where a minus sign would be -- the Wallet's own
|
|
225
|
+
* past-zero screen (boostledger, Paper 201 screen 03), followed rather than
|
|
226
|
+
* inventing a third look. Nothing is broken and the copy does not say it is;
|
|
227
|
+
* what happens next is the consumer's `burnNote`, because the overdraft limit is
|
|
228
|
+
* the Ledger's. At zero or above it is the plain balance.
|
|
229
|
+
*
|
|
230
|
+
* **Top bar only.** The sidebar card has no drawing for this and the redesigned
|
|
231
|
+
* shell does not render it, so the type requires `placement="topbar"` rather
|
|
232
|
+
* than leaving the card to guess. No board draws either form: they are new
|
|
233
|
+
* undrawn states for the person at the gate to judge.
|
|
234
|
+
*/
|
|
235
|
+
export interface WalletChipTotalOnlyProps extends WalletChipChromeProps {
|
|
236
|
+
placement: 'topbar';
|
|
237
|
+
totalOnly: true;
|
|
238
|
+
unknown?: false;
|
|
239
|
+
/**
|
|
240
|
+
* The balance, signed, in whole tokens: `granted + purchased` from the
|
|
241
|
+
* Ledger's signed figures, which its single balance row guarantees is the
|
|
242
|
+
* balance.
|
|
243
|
+
*/
|
|
244
|
+
balance: number;
|
|
245
|
+
/**
|
|
246
|
+
* What happens next, preformatted, e.g. "Work carries on for another 930
|
|
247
|
+
* tokens, then paid actions stop." The consumer's, because the overdraft limit
|
|
248
|
+
* is the Ledger's, not the chip's.
|
|
249
|
+
*/
|
|
250
|
+
burnNote?: string;
|
|
251
|
+
granted?: never;
|
|
252
|
+
purchased?: never;
|
|
253
|
+
grantCycle?: never;
|
|
254
|
+
burnedFraction?: never;
|
|
255
|
+
grantExpires?: never;
|
|
256
|
+
}
|
|
257
|
+
export declare function WalletChip(props: WalletChipProps): import("react").JSX.Element;
|
|
183
258
|
export {};
|