@mohasinac/appkit 4.62.1 → 4.63.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 (261) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +8 -29
  3. package/dist/_internal/client/features/layout/filterNavItems.d.ts +28 -0
  4. package/dist/_internal/client/features/layout/filterNavItems.js +35 -0
  5. package/dist/_internal/server/features/ads/booking.d.ts +118 -0
  6. package/dist/_internal/server/features/ads/booking.js +326 -0
  7. package/dist/_internal/server/features/checkout/actions.d.ts +51 -11
  8. package/dist/_internal/server/features/checkout/actions.js +335 -80
  9. package/dist/_internal/server/features/checkout/expand-order-items.d.ts +1 -2
  10. package/dist/_internal/server/features/checkout/expand-order-items.js +32 -0
  11. package/dist/_internal/server/features/checkout/freeze-parts.d.ts +50 -0
  12. package/dist/_internal/server/features/checkout/freeze-parts.js +94 -0
  13. package/dist/_internal/server/features/checkout/value-otp-gate.d.ts +55 -0
  14. package/dist/_internal/server/features/checkout/value-otp-gate.js +60 -0
  15. package/dist/_internal/server/features/dashboard/attention.d.ts +128 -0
  16. package/dist/_internal/server/features/dashboard/attention.js +183 -0
  17. package/dist/_internal/server/features/dashboard/data.d.ts +47 -0
  18. package/dist/_internal/server/features/dashboard/data.js +82 -0
  19. package/dist/_internal/server/features/inventory/reservations.d.ts +99 -0
  20. package/dist/_internal/server/features/inventory/reservations.js +273 -0
  21. package/dist/_internal/server/features/offers/destinations.d.ts +40 -0
  22. package/dist/_internal/server/features/offers/destinations.js +65 -0
  23. package/dist/_internal/server/features/orders/adapters.js +21 -0
  24. package/dist/_internal/server/features/orders/amend-delivery.d.ts +56 -0
  25. package/dist/_internal/server/features/orders/amend-delivery.js +124 -0
  26. package/dist/_internal/server/features/orders/balance.d.ts +89 -0
  27. package/dist/_internal/server/features/orders/balance.js +256 -0
  28. package/dist/_internal/server/features/pricing/actions.d.ts +58 -0
  29. package/dist/_internal/server/features/pricing/actions.js +144 -0
  30. package/dist/_internal/server/features/shipping/book-split.d.ts +86 -0
  31. package/dist/_internal/server/features/shipping/book-split.js +130 -0
  32. package/dist/_internal/server/features/shipping/book.d.ts +61 -0
  33. package/dist/_internal/server/features/shipping/book.js +176 -0
  34. package/dist/_internal/server/features/shipping/courier-block.js +26 -1
  35. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  36. package/dist/_internal/server/features/site-settings/adapters.js +14 -0
  37. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  38. package/dist/_internal/server/features/stores/adapters.js +14 -0
  39. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  40. package/dist/_internal/server/functions/scheduled.js +57 -1
  41. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +2 -0
  42. package/dist/_internal/server/jobs/core/jobRunners.js +45 -0
  43. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +4 -1
  44. package/dist/_internal/server/jobs/core/reservationExpiry.d.ts +17 -0
  45. package/dist/_internal/server/jobs/core/reservationExpiry.js +40 -0
  46. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +15 -0
  47. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  48. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  49. package/dist/_internal/server/jobs/handlers/reservationExpiry.d.ts +2 -0
  50. package/dist/_internal/server/jobs/handlers/reservationExpiry.js +2 -0
  51. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  52. package/dist/_internal/shared/actions/action-registry.js +268 -0
  53. package/dist/_internal/shared/checkout/order-math.d.ts +31 -2
  54. package/dist/_internal/shared/checkout/order-math.js +22 -36
  55. package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
  56. package/dist/_internal/shared/checkout/rules/index.js +1 -1
  57. package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +18 -0
  58. package/dist/_internal/shared/checkout/rules/payment-gate.js +25 -0
  59. package/dist/_internal/shared/checkout/split.d.ts +0 -12
  60. package/dist/_internal/shared/checkout/split.js +22 -2
  61. package/dist/_internal/shared/content/disclaimers.js +7 -7
  62. package/dist/_internal/shared/content/published-values.d.ts +6 -1
  63. package/dist/_internal/shared/content/published-values.js +8 -0
  64. package/dist/_internal/shared/features/admin/integrations.d.ts +32 -2
  65. package/dist/_internal/shared/features/admin/integrations.js +19 -9
  66. package/dist/_internal/shared/features/dashboard/hub-sections.d.ts +72 -0
  67. package/dist/_internal/shared/features/dashboard/hub-sections.js +75 -0
  68. package/dist/_internal/shared/features/emi/choice.d.ts +98 -0
  69. package/dist/_internal/shared/features/emi/choice.js +69 -0
  70. package/dist/_internal/shared/features/emi/group.d.ts +84 -0
  71. package/dist/_internal/shared/features/emi/group.js +126 -0
  72. package/dist/_internal/shared/features/ledger/posting.d.ts +107 -0
  73. package/dist/_internal/shared/features/ledger/posting.js +165 -0
  74. package/dist/_internal/shared/features/ledger/reports.d.ts +76 -0
  75. package/dist/_internal/shared/features/ledger/reports.js +127 -0
  76. package/dist/_internal/shared/features/ledger/rules.d.ts +170 -0
  77. package/dist/_internal/shared/features/ledger/rules.js +221 -0
  78. package/dist/_internal/shared/features/offers/coordination.d.ts +122 -0
  79. package/dist/_internal/shared/features/offers/coordination.js +80 -0
  80. package/dist/_internal/shared/features/orders/delivery-snapshot.d.ts +114 -0
  81. package/dist/_internal/shared/features/orders/delivery-snapshot.js +116 -0
  82. package/dist/_internal/shared/features/payouts/pay-period.d.ts +91 -0
  83. package/dist/_internal/shared/features/payouts/pay-period.js +104 -0
  84. package/dist/_internal/shared/features/points/accepts.d.ts +1 -8
  85. package/dist/_internal/shared/features/points/accepts.js +12 -6
  86. package/dist/_internal/shared/features/pricing/rate-registry.d.ts +150 -0
  87. package/dist/_internal/shared/features/pricing/rate-registry.js +246 -0
  88. package/dist/_internal/shared/features/shipping/parcel.d.ts +123 -0
  89. package/dist/_internal/shared/features/shipping/parcel.js +139 -0
  90. package/dist/_internal/shared/features/site-settings/fees.d.ts +16 -8
  91. package/dist/_internal/shared/features/site-settings/fees.js +12 -8
  92. package/dist/_internal/shared/fees/explain.d.ts +81 -0
  93. package/dist/_internal/shared/fees/explain.js +101 -0
  94. package/dist/_internal/shared/history/index.d.ts +1 -1
  95. package/dist/_internal/shared/history/index.js +1 -1
  96. package/dist/_internal/shared/history/types.d.ts +7 -1
  97. package/dist/_internal/shared/history/types.js +25 -0
  98. package/dist/client.d.ts +11 -1
  99. package/dist/client.js +13 -0
  100. package/dist/constants/api-endpoints.d.ts +72 -0
  101. package/dist/constants/api-endpoints.js +41 -0
  102. package/dist/constants/field-names.d.ts +41 -0
  103. package/dist/constants/field-names.js +47 -0
  104. package/dist/features/account/components/UserOrdersView.js +28 -2
  105. package/dist/features/admin/components/AdminIntegrationsView.d.ts +55 -0
  106. package/dist/features/admin/components/AdminIntegrationsView.js +74 -0
  107. package/dist/features/admin/components/AdminOrderEditorView.d.ts +16 -1
  108. package/dist/features/admin/components/AdminOrderEditorView.js +72 -3
  109. package/dist/features/admin/components/AdminOrdersView.js +20 -1
  110. package/dist/features/admin/schemas/firestore.d.ts +32 -0
  111. package/dist/features/ads/schemas/booking-write.d.ts +120 -0
  112. package/dist/features/ads/schemas/booking-write.js +75 -0
  113. package/dist/features/ads/schemas/creative-form.d.ts +18 -0
  114. package/dist/features/ads/schemas/creative-form.js +51 -0
  115. package/dist/features/ads/schemas/firestore.d.ts +140 -0
  116. package/dist/features/ads/schemas/firestore.js +109 -0
  117. package/dist/features/auctions/repository/bid.repository.js +3 -1
  118. package/dist/features/audit-log/schemas/firestore.d.ts +110 -0
  119. package/dist/features/audit-log/schemas/firestore.js +74 -0
  120. package/dist/features/auth/actions/profile-actions.d.ts +69 -1
  121. package/dist/features/auth/actions/profile-actions.js +62 -1
  122. package/dist/features/auth/schemas/firestore.d.ts +16 -2
  123. package/dist/features/auth/schemas/firestore.js +2 -1
  124. package/dist/features/blog/actions/blog-actions.d.ts +6 -0
  125. package/dist/features/blog/actions/blog-actions.js +17 -2
  126. package/dist/features/blog/api/route.js +4 -1
  127. package/dist/features/blog/columns/index.js +5 -0
  128. package/dist/features/blog/components/BlogIndexPageView.js +2 -1
  129. package/dist/features/blog/repository/blog.repository.d.ts +4 -0
  130. package/dist/features/blog/repository/blog.repository.js +5 -0
  131. package/dist/features/blog/schemas/blog-form.d.ts +3 -0
  132. package/dist/features/blog/schemas/blog-form.js +1 -0
  133. package/dist/features/blog/schemas/firestore.d.ts +38 -1
  134. package/dist/features/blog/schemas/firestore.js +23 -0
  135. package/dist/features/blog/schemas/index.d.ts +6 -0
  136. package/dist/features/blog/schemas/index.js +2 -0
  137. package/dist/features/blog/types/index.d.ts +2 -0
  138. package/dist/features/cart/components/CartPriceBreakdown.d.ts +12 -1
  139. package/dist/features/cart/components/CartPriceBreakdown.js +24 -5
  140. package/dist/features/cart/repository/cart.repository.d.ts +14 -0
  141. package/dist/features/cart/repository/cart.repository.js +20 -0
  142. package/dist/features/cart/schemas/firestore.d.ts +24 -0
  143. package/dist/features/dashboard/components/DashboardHubView.d.ts +83 -0
  144. package/dist/features/dashboard/components/DashboardHubView.js +56 -0
  145. package/dist/features/dashboard/components/DashboardSectionsClient.d.ts +23 -0
  146. package/dist/features/dashboard/components/DashboardSectionsClient.js +66 -0
  147. package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.d.ts +55 -0
  148. package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.js +175 -0
  149. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +24 -0
  150. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +28 -0
  151. package/dist/features/dispute-reasons/schemas/firestore.d.ts +3 -5
  152. package/dist/features/events/components/EventSaleBanner.js +1 -1
  153. package/dist/features/faq/components/FAQPageContent.js +2 -2
  154. package/dist/features/gateway-events/schemas/index.d.ts +2 -2
  155. package/dist/features/homepage/schemas/index.d.ts +8 -8
  156. package/dist/features/inventory/schemas/stock-reservation.d.ts +114 -0
  157. package/dist/features/inventory/schemas/stock-reservation.js +103 -0
  158. package/dist/features/layout/AppLayoutShell.d.ts +9 -1
  159. package/dist/features/layout/AppLayoutShell.js +12 -4
  160. package/dist/features/layout/BottomActions.js +2 -1
  161. package/dist/features/layout/BottomNavbar.d.ts +10 -2
  162. package/dist/features/layout/SideActionDock.js +52 -2
  163. package/dist/features/layout/TitleBarLayout.js +13 -4
  164. package/dist/features/layout/TitleBarLayout.style.css +59 -0
  165. package/dist/features/layout/useCtaReveal.d.ts +16 -1
  166. package/dist/features/layout/useCtaReveal.js +17 -26
  167. package/dist/features/ledger/repository/journal.repository.d.ts +110 -0
  168. package/dist/features/ledger/repository/journal.repository.js +312 -0
  169. package/dist/features/ledger/schemas/expenses.d.ts +87 -0
  170. package/dist/features/ledger/schemas/expenses.js +27 -0
  171. package/dist/features/ledger/schemas/firestore.d.ts +183 -0
  172. package/dist/features/ledger/schemas/firestore.js +69 -0
  173. package/dist/features/orders/components/BalancePaymentPanel.d.ts +12 -0
  174. package/dist/features/orders/components/BalancePaymentPanel.js +94 -0
  175. package/dist/features/orders/components/RaiseDisputeForm.js +19 -11
  176. package/dist/features/orders/repository/orders.repository.d.ts +28 -1
  177. package/dist/features/orders/repository/orders.repository.js +60 -6
  178. package/dist/features/orders/schemas/balance.d.ts +40 -0
  179. package/dist/features/orders/schemas/balance.js +47 -0
  180. package/dist/features/orders/schemas/firestore.d.ts +202 -0
  181. package/dist/features/orders/schemas/firestore.js +43 -0
  182. package/dist/features/orders/schemas/index.d.ts +2 -2
  183. package/dist/features/orders/types/index.d.ts +21 -0
  184. package/dist/features/payments/repository/payout.repository.js +3 -2
  185. package/dist/features/payments/schemas/firestore.d.ts +92 -1
  186. package/dist/features/payments/schemas/firestore.js +23 -0
  187. package/dist/features/pricing/components/AdminPricingView.d.ts +13 -0
  188. package/dist/features/pricing/components/AdminPricingView.js +157 -0
  189. package/dist/features/products/schemas/firestore.d.ts +26 -1
  190. package/dist/features/products/schemas/firestore.js +2 -0
  191. package/dist/features/products/schemas/index.d.ts +2 -2
  192. package/dist/features/reviews/repository/reviews.repository.d.ts +14 -0
  193. package/dist/features/reviews/repository/reviews.repository.js +16 -0
  194. package/dist/features/scams/repository/scammer.repository.js +2 -1
  195. package/dist/features/scams/schemas/firestore.d.ts +2 -1
  196. package/dist/features/scams/schemas/firestore.js +2 -1
  197. package/dist/features/seller/actions/offer-actions.js +56 -0
  198. package/dist/features/seller/actions/seller-actions.d.ts +1 -1
  199. package/dist/features/seller/actions/seller-actions.js +4 -0
  200. package/dist/features/seller/components/DispatchForm.d.ts +11 -0
  201. package/dist/features/seller/components/DispatchForm.js +190 -0
  202. package/dist/features/seller/components/SellerOrdersView.js +94 -2
  203. package/dist/features/seller/components/index.d.ts +1 -0
  204. package/dist/features/seller/components/index.js +1 -0
  205. package/dist/features/seller/repository/offer.repository.js +4 -3
  206. package/dist/features/seller/schemas/firestore.d.ts +23 -2
  207. package/dist/features/seller/schemas/firestore.js +3 -2
  208. package/dist/features/seller/schemas/index.d.ts +4 -4
  209. package/dist/features/seller/schemas/order-forms.d.ts +61 -4
  210. package/dist/features/seller/schemas/order-forms.js +41 -8
  211. package/dist/features/shipments/schemas/firestore.d.ts +25 -0
  212. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +39 -0
  213. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +55 -0
  214. package/dist/features/shipping-boxes/schemas/firestore.d.ts +2 -4
  215. package/dist/features/stores/repository/store.repository.d.ts +18 -0
  216. package/dist/features/stores/repository/store.repository.js +27 -1
  217. package/dist/features/stores/schemas/firestore.d.ts +41 -0
  218. package/dist/features/stores/schemas/firestore.js +20 -0
  219. package/dist/features/stores/schemas/index.d.ts +6 -6
  220. package/dist/features/support/repository/support.repository.js +72 -6
  221. package/dist/features/support/schemas/firestore.d.ts +17 -0
  222. package/dist/features/support/schemas/index.d.ts +2 -2
  223. package/dist/index.d.ts +5 -0
  224. package/dist/index.js +19 -0
  225. package/dist/next/routing/route-map.d.ts +92 -0
  226. package/dist/next/routing/route-map.js +53 -0
  227. package/dist/react/contexts/SessionContext.d.ts +17 -1
  228. package/dist/schemas/registry.d.ts +22 -16
  229. package/dist/security/pii-schemas.d.ts +1 -1
  230. package/dist/security/pii-schemas.js +45 -0
  231. package/dist/server.d.ts +27 -1
  232. package/dist/server.js +144 -0
  233. package/dist/styles.css +349 -1
  234. package/dist/tailwind-utilities.css +1 -1
  235. package/dist/ui/components/Button.d.ts +10 -1
  236. package/dist/ui/components/Button.js +25 -3
  237. package/dist/ui/components/Chart.d.ts +63 -0
  238. package/dist/ui/components/Chart.js +169 -0
  239. package/dist/ui/components/Chart.style.css +214 -0
  240. package/dist/ui/components/Disclaimer.js +10 -1
  241. package/dist/ui/components/OverflowRow.js +54 -2
  242. package/dist/ui/components/OverflowRow.style.css +38 -0
  243. package/dist/ui/components/RichTextEditor.js +99 -8
  244. package/dist/ui/components/chart-spec.d.ts +68 -0
  245. package/dist/ui/components/chart-spec.js +140 -0
  246. package/dist/ui/components/index.style.css +1 -0
  247. package/dist/ui/components/surface-tokens.d.ts +1 -1
  248. package/dist/ui/components/surface-tokens.js +9 -0
  249. package/dist/ui/components/toolbar-collapse.d.ts +13 -0
  250. package/dist/ui/components/toolbar-collapse.js +10 -1
  251. package/dist/ui/index.d.ts +6 -0
  252. package/dist/ui/index.js +3 -0
  253. package/dist/ui/rich-text/RichText.d.ts +10 -1
  254. package/dist/ui/rich-text/RichText.js +33 -2
  255. package/dist/ui/rich-text/RichText.style.css +35 -0
  256. package/dist/ui/rich-text/RichTextRenderer.d.ts +30 -1
  257. package/dist/ui/rich-text/RichTextRenderer.js +34 -2
  258. package/dist/ui/rich-text/chart-blocks.d.ts +62 -0
  259. package/dist/ui/rich-text/chart-blocks.js +87 -0
  260. package/package.json +1 -1
  261. package/scripts/seed-cli.mjs +3 -0
@@ -76,6 +76,15 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
76
76
  * Children and aria-label props override the action defaults when explicitly provided.
77
77
  */
78
78
  action?: ActionDef;
79
+ /**
80
+ * Replace the confirmation dialog's BODY for this one render.
81
+ *
82
+ * Does not create a dialog: ignored unless `action.confirmation` exists, so
83
+ * the registry still decides whether an action is confirmed at all. This
84
+ * only substitutes the sentence, for the case where the consequence is data
85
+ * the registry should not hold a copy of. See the note at `confirmBody`.
86
+ */
87
+ confirmationBody?: React.ReactNode;
79
88
  }
80
- export declare function Button({ variant, size, className, isLoading, disabled, children, gap, textSize, paddingX, paddingY, weight, textColor, border, rounded, shadow, justify, asChild, action, ...props }: ButtonProps): React.JSX.Element;
89
+ export declare function Button({ variant, size, className, isLoading, disabled, children, gap, textSize, paddingX, paddingY, weight, textColor, border, rounded, shadow, justify, asChild, action, confirmationBody, ...props }: ButtonProps): React.JSX.Element;
81
90
  export {};
@@ -143,7 +143,7 @@ const ACTION_ICON_SIZE = {
143
143
  md: "md",
144
144
  lg: "lg",
145
145
  };
146
- function ActionIcon({ action, size, }) {
146
+ function ActionIcon({ action, confirmationBody, size, }) {
147
147
  const Glyph = resolveIcon(action?.iconKey);
148
148
  if (!Glyph)
149
149
  return null;
@@ -158,7 +158,7 @@ const ACTION_KIND_VARIANT = {
158
158
  link: "ghost",
159
159
  chip: "outline",
160
160
  };
161
- export function Button({ variant, size = "md", className = "", isLoading = false, disabled, children, gap, textSize, paddingX, paddingY, weight, textColor, border, rounded, shadow, justify, asChild = false, action, ...props }) {
161
+ export function Button({ variant, size = "md", className = "", isLoading = false, disabled, children, gap, textSize, paddingX, paddingY, weight, textColor, border, rounded, shadow, justify, asChild = false, action, confirmationBody, ...props }) {
162
162
  const [confirmOpen, setConfirmOpen] = useState(false);
163
163
  // React 17+ no longer pools SyntheticEvents, so it's safe to hold onto the
164
164
  // original triggering event across the confirmation dialog's async gap and
@@ -242,6 +242,28 @@ export function Button({ variant, size = "md", className = "", isLoading = false
242
242
  const confirmVariant = confirmDef?.confirmKind
243
243
  ? (ACTION_KIND_VARIANT[confirmDef.confirmKind] ?? "primary")
244
244
  : "primary";
245
+ /*
246
+ * The body a caller supplied, when the consequence is DATA rather than copy.
247
+ *
248
+ * 🛑 It cannot CREATE a dialog — `confirmPortal` still gates on
249
+ * `action.confirmation` existing. So the registry keeps every decision that
250
+ * matters: whether this action is confirmed at all, its title, its confirm
251
+ * label, and its destructive kind. Only the sentence in the middle can be
252
+ * substituted, and only when the ActionDef has already declared that there
253
+ * IS one.
254
+ *
255
+ * Why the seam is needed: `/admin/integrations` renders one row per
256
+ * integration, and "what switching this off does" genuinely differs per row
257
+ * — payments abandons in-flight payments, support hides a contact route,
258
+ * shipping selects a working manual path and stops nothing. That text already
259
+ * lives on `INTEGRATIONS[id].offConsequence`, which is also what the row
260
+ * itself renders. The alternatives were worse in both directions: four
261
+ * near-duplicate ActionDefs with the consequence copied into the registry (a
262
+ * second place for one sentence to drift from — Root Cause #42), or a generic
263
+ * "are you sure?" that flattens exactly the distinction the model exists to
264
+ * preserve.
265
+ */
266
+ const confirmBody = confirmationBody ?? confirmDef?.body;
245
267
  // Built once and rendered alongside EITHER the asChild clone or the plain
246
268
  // <button> below — this must never live inside an early return for only
247
269
  // one of those two branches, or the other branch can never open a
@@ -258,7 +280,7 @@ export function Button({ variant, size = "md", className = "", isLoading = false
258
280
  padding: "1.5rem",
259
281
  maxWidth: "380px", width: "calc(100% - 2rem)",
260
282
  boxShadow: "var(--appkit-shadow-lg, 0 20px 60px rgba(0,0,0,0.2))",
261
- }, children: [_jsx("p", { id: "appkit-action-confirm-title", style: { fontWeight: 600, marginBottom: "0.5rem", fontSize: "1rem" }, children: confirmDef.title }), _jsx("p", { style: { fontSize: "0.875rem", color: "var(--appkit-color-text-muted)", marginBottom: "1.25rem" }, children: confirmDef.body }), _jsxs("div", { style: { display: "flex", gap: "0.5rem", justifyContent: "flex-end" }, children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setConfirmOpen(false), children: confirmDef.cancelLabel ?? "Cancel" }), _jsx(Button, { variant: confirmVariant, size: "sm", onClick: handleConfirm, children: confirmDef.confirmLabel })] })] }) }), document.body);
283
+ }, children: [_jsx("p", { id: "appkit-action-confirm-title", style: { fontWeight: 600, marginBottom: "0.5rem", fontSize: "1rem" }, children: confirmDef.title }), _jsx("p", { style: { fontSize: "0.875rem", color: "var(--appkit-color-text-muted)", marginBottom: "1.25rem" }, children: confirmBody }), _jsxs("div", { style: { display: "flex", gap: "0.5rem", justifyContent: "flex-end" }, children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setConfirmOpen(false), children: confirmDef.cancelLabel ?? "Cancel" }), _jsx(Button, { variant: confirmVariant, size: "sm", onClick: handleConfirm, children: confirmDef.confirmLabel })] })] }) }), document.body);
262
284
  if (asChild && React.isValidElement(resolvedChildren)) {
263
285
  const child = resolvedChildren;
264
286
  // Never spread the caller's raw onClick here — it must go through
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The one chart primitive — C-chart.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * Three features had already hand-rolled the same thing:
7
+ * `AdminAnalyticsCharts`, `SellerRevenueChart` and `TesterFeedbackChart` each
8
+ * re-declare the same eight `dynamic(() => import("recharts"))` wrappers with
9
+ * the same `as React.ComponentType<any>` cast and their own hardcoded fills.
10
+ * Three copies is the Duplication Framework's Rule-of-Three trigger, so this is
11
+ * consolidation of something that exists, not speculative infrastructure.
12
+ *
13
+ * It is also what lets a chart live in CMS content: `ChartSpec` is a plain
14
+ * JSON-serialisable object, so a rich-text author can embed one as
15
+ * `<figure data-chart='{...}'>` and the renderer hydrates it here.
16
+ *
17
+ * The data contract and its validator live in `./chart-spec`, which is pure —
18
+ * see that file's header for why the split matters.
19
+ *
20
+ * ## 🛑 recharts is client-only, and that is not incidental
21
+ *
22
+ * Every recharts component is behind `dynamic(..., { ssr: false })`, so this
23
+ * module renders its chrome on the server and the plot only in the browser.
24
+ * `RichTextRenderer` is a Server Component; it can mount this because this is a
25
+ * client component, but nothing here may be called during SSR.
26
+ *
27
+ * ## 🛑 The palette lives in CSS and NOWHERE ELSE
28
+ *
29
+ * There is deliberately no colour literal in this file. An earlier draft kept a
30
+ * `FALLBACK_SERIES_COLORS` array of eight hexes beside the identical eight in
31
+ * `Chart.style.css` — which is a denormalised mirror of exactly the kind Root
32
+ * Cause #42 is about: the day someone re-steps a hue for contrast, they fix one
33
+ * copy, and the chart renders one colour in CSS and a different one in JS with
34
+ * nothing reporting the disagreement.
35
+ *
36
+ * So the colours are read from the cascade at runtime, and if the stylesheet is
37
+ * absent the chart renders with recharts' own defaults plus a one-time console
38
+ * warning naming the likely cause. A wrong-but-plausible colour is worse than
39
+ * an obviously unstyled one.
40
+ *
41
+ * Colour is read off THIS element rather than from `useTheme()`, which throws
42
+ * without a `<ThemeProvider>` — a chart has to survive a preview iframe, an
43
+ * email preview or a test harness, the same reason `useCanSeePrices` uses
44
+ * `useOptionalSession` (Root Cause #20).
45
+ */
46
+ import React from "react";
47
+ import { type ChartSpec } from "./chart-spec";
48
+ export interface ChartProps {
49
+ spec: ChartSpec;
50
+ /** Plot height in px. Default 300. */
51
+ height?: number;
52
+ className?: string;
53
+ /** Hide the table-view toggle. The legend is never optional for ≥2 series. */
54
+ hideTableToggle?: boolean;
55
+ }
56
+ export declare function Chart({ spec, height, className, hideTableToggle, }: ChartProps): React.JSX.Element;
57
+ /**
58
+ * Render a `ChartSpec` from untrusted JSON, showing the validation reason when
59
+ * it does not parse — never rendering nothing.
60
+ */
61
+ export declare function ChartFromJson({ json, ...rest }: {
62
+ json: string;
63
+ } & Omit<ChartProps, "spec">): React.JSX.Element;
@@ -0,0 +1,169 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ /**
4
+ * The one chart primitive — C-chart.
5
+ *
6
+ * ## Why this exists
7
+ *
8
+ * Three features had already hand-rolled the same thing:
9
+ * `AdminAnalyticsCharts`, `SellerRevenueChart` and `TesterFeedbackChart` each
10
+ * re-declare the same eight `dynamic(() => import("recharts"))` wrappers with
11
+ * the same `as React.ComponentType<any>` cast and their own hardcoded fills.
12
+ * Three copies is the Duplication Framework's Rule-of-Three trigger, so this is
13
+ * consolidation of something that exists, not speculative infrastructure.
14
+ *
15
+ * It is also what lets a chart live in CMS content: `ChartSpec` is a plain
16
+ * JSON-serialisable object, so a rich-text author can embed one as
17
+ * `<figure data-chart='{...}'>` and the renderer hydrates it here.
18
+ *
19
+ * The data contract and its validator live in `./chart-spec`, which is pure —
20
+ * see that file's header for why the split matters.
21
+ *
22
+ * ## 🛑 recharts is client-only, and that is not incidental
23
+ *
24
+ * Every recharts component is behind `dynamic(..., { ssr: false })`, so this
25
+ * module renders its chrome on the server and the plot only in the browser.
26
+ * `RichTextRenderer` is a Server Component; it can mount this because this is a
27
+ * client component, but nothing here may be called during SSR.
28
+ *
29
+ * ## 🛑 The palette lives in CSS and NOWHERE ELSE
30
+ *
31
+ * There is deliberately no colour literal in this file. An earlier draft kept a
32
+ * `FALLBACK_SERIES_COLORS` array of eight hexes beside the identical eight in
33
+ * `Chart.style.css` — which is a denormalised mirror of exactly the kind Root
34
+ * Cause #42 is about: the day someone re-steps a hue for contrast, they fix one
35
+ * copy, and the chart renders one colour in CSS and a different one in JS with
36
+ * nothing reporting the disagreement.
37
+ *
38
+ * So the colours are read from the cascade at runtime, and if the stylesheet is
39
+ * absent the chart renders with recharts' own defaults plus a one-time console
40
+ * warning naming the likely cause. A wrong-but-plausible colour is worse than
41
+ * an obviously unstyled one.
42
+ *
43
+ * Colour is read off THIS element rather than from `useTheme()`, which throws
44
+ * without a `<ThemeProvider>` — a chart has to survive a preview iframe, an
45
+ * email preview or a test harness, the same reason `useCanSeePrices` uses
46
+ * `useOptionalSession` (Root Cause #20).
47
+ */
48
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
49
+ import dynamic from "next/dynamic.js";
50
+ import { CHART_MAX_SERIES, parseChartAttr, } from "./chart-spec.js";
51
+ /*
52
+ * recharts exports generic components (<DataPointType>) whose inferred props do
53
+ * not satisfy `dynamic()`'s ComponentType constraint, so each needs a cast.
54
+ * This is the SINGLE copy of that cast in the codebase — the three feature
55
+ * charts above should migrate onto this module rather than repeating it.
56
+ */
57
+ const ResponsiveContainer = dynamic(() => import("recharts").then((m) => m.ResponsiveContainer), { ssr: false });
58
+ const BarChart = dynamic(() => import("recharts").then((m) => m.BarChart), { ssr: false });
59
+ const LineChart = dynamic(() => import("recharts").then((m) => m.LineChart), { ssr: false });
60
+ const AreaChart = dynamic(() => import("recharts").then((m) => m.AreaChart), { ssr: false });
61
+ const Bar = dynamic(() => import("recharts").then((m) => m.Bar), { ssr: false });
62
+ const Line = dynamic(() => import("recharts").then((m) => m.Line), { ssr: false });
63
+ const Area = dynamic(() => import("recharts").then((m) => m.Area), { ssr: false });
64
+ const XAxis = dynamic(() => import("recharts").then((m) => m.XAxis), { ssr: false });
65
+ const YAxis = dynamic(() => import("recharts").then((m) => m.YAxis), { ssr: false });
66
+ const CartesianGrid = dynamic(() => import("recharts").then((m) => m.CartesianGrid), { ssr: false });
67
+ const Tooltip = dynamic(() => import("recharts").then((m) => m.Tooltip), { ssr: false });
68
+ /** `""` → `undefined`, so recharts falls back to its own default. */
69
+ const orUndef = (s) => (s.length > 0 ? s : undefined);
70
+ let warnedMissingStylesheet = false;
71
+ /**
72
+ * Resolve the palette from the element's own computed style, and re-resolve
73
+ * whenever the active theme changes.
74
+ *
75
+ * Two triggers, because there are two ways the theme can move: the viewer's
76
+ * toggle (which stamps `data-theme` on `<html>`) and the OS preference (which
77
+ * only the media query sees). Watching one and not the other leaves a chart
78
+ * stranded in the previous palette.
79
+ *
80
+ * Returns `null` until the first read completes — one frame after mount, which
81
+ * is invisible given the plot itself is behind `ssr: false` anyway.
82
+ */
83
+ function useChartPalette(ref) {
84
+ const [palette, setPalette] = useState(null);
85
+ const read = useCallback(() => {
86
+ const el = ref.current;
87
+ if (!el || typeof window === "undefined")
88
+ return;
89
+ const cs = window.getComputedStyle(el);
90
+ const v = (name) => cs.getPropertyValue(name).trim();
91
+ const series = Array.from({ length: CHART_MAX_SERIES }, (_, i) => v(`--chart-${i + 1}`));
92
+ if (!series[0] && !warnedMissingStylesheet) {
93
+ warnedMissingStylesheet = true;
94
+ // eslint-disable-next-line no-console
95
+ console.warn("[appkit] <Chart> found no --chart-* custom properties. " +
96
+ 'Import the stylesheet once at your app root: import "@mohasinac/appkit/styles". ' +
97
+ "Rendering with recharts' default colours until then.");
98
+ }
99
+ setPalette({
100
+ series,
101
+ grid: v("--chart-grid"),
102
+ axis: v("--chart-axis"),
103
+ inkMuted: v("--chart-ink-muted"),
104
+ });
105
+ }, [ref]);
106
+ useEffect(() => {
107
+ read();
108
+ if (typeof window === "undefined")
109
+ return;
110
+ const observer = new MutationObserver(read);
111
+ observer.observe(document.documentElement, {
112
+ attributes: true,
113
+ attributeFilter: ["data-theme", "style", "class"],
114
+ });
115
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
116
+ mq.addEventListener("change", read);
117
+ return () => {
118
+ observer.disconnect();
119
+ mq.removeEventListener("change", read);
120
+ };
121
+ }, [read]);
122
+ return palette;
123
+ }
124
+ const fmt = (n, unit) => `${Number.isInteger(n) ? n : n.toFixed(2)}${unit ? ` ${unit}` : ""}`;
125
+ export function Chart({ spec, height = 300, className = "", hideTableToggle = false, }) {
126
+ const rootRef = useRef(null);
127
+ const palette = useChartPalette(rootRef);
128
+ const [showTable, setShowTable] = useState(false);
129
+ /** recharts wants row-per-category; the spec is column-per-series. */
130
+ const rows = useMemo(() => spec.categories.map((cat, i) => {
131
+ const row = { category: cat };
132
+ for (const s of spec.series)
133
+ row[s.label] = s.values[i] ?? 0;
134
+ return row;
135
+ }), [spec]);
136
+ const multi = spec.series.length >= 2;
137
+ const accessibleName = spec.title ?? spec.caption ?? "Chart";
138
+ const colorAt = (i) => orUndef(palette?.series[i % CHART_MAX_SERIES] ?? "");
139
+ const renderTable = () => (_jsx("div", { className: "appkit-chart__scroll", children: _jsxs("table", { className: "appkit-chart__table", children: [_jsx("caption", { className: "appkit-chart__caption", children: accessibleName }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Category" }), spec.series.map((s) => (_jsxs("th", { scope: "col", children: [s.label, spec.unit ? ` (${spec.unit})` : ""] }, s.label)))] }) }), _jsx("tbody", { children: spec.categories.map((cat, i) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: cat }), spec.series.map((s) => (_jsx("td", { children: fmt(s.values[i] ?? 0, spec.unit) }, s.label)))] }, cat))) })] }) }));
140
+ const tooltip = (_jsx(Tooltip, { content: (props) => {
141
+ if (!props?.active || !Array.isArray(props.payload) || !props.payload.length) {
142
+ return null;
143
+ }
144
+ return (_jsxs("div", { className: "appkit-chart__tooltip", children: [_jsx("div", { className: "appkit-chart__tooltip-label", children: props.label }), props.payload.map((p) => (_jsxs("div", { className: "appkit-chart__tooltip-row", children: [_jsx("span", { style: { color: p.color }, children: String(p.dataKey) }), _jsx("span", { children: fmt(Number(p.value), spec.unit) })] }, String(p.dataKey))))] }));
145
+ } }));
146
+ const axes = (_jsxs(_Fragment, { children: [_jsx(CartesianGrid, { stroke: orUndef(palette?.grid ?? ""), vertical: false }), _jsx(XAxis, { dataKey: "category", stroke: orUndef(palette?.axis ?? ""), tick: { fill: orUndef(palette?.inkMuted ?? ""), fontSize: 12 }, tickLine: false }), _jsx(YAxis, { stroke: orUndef(palette?.axis ?? ""), tick: { fill: orUndef(palette?.inkMuted ?? ""), fontSize: 12 }, tickLine: false, width: 48 }), tooltip] }));
147
+ let plot;
148
+ if (spec.kind === "bar" || spec.kind === "groupedBar") {
149
+ plot = (_jsxs(BarChart, { data: rows, barGap: 2, barCategoryGap: "20%", children: [axes, spec.series.map((s, i) => (_jsx(Bar, { dataKey: s.label, fill: colorAt(i), radius: [4, 4, 0, 0] }, s.label)))] }));
150
+ }
151
+ else if (spec.kind === "line") {
152
+ plot = (_jsxs(LineChart, { data: rows, children: [axes, spec.series.map((s, i) => (_jsx(Line, { type: "monotone", dataKey: s.label, stroke: colorAt(i), strokeWidth: 2, dot: { r: 3 }, activeDot: { r: 5 } }, s.label)))] }));
153
+ }
154
+ else {
155
+ plot = (_jsxs(AreaChart, { data: rows, children: [axes, spec.series.map((s, i) => (_jsx(Area, { type: "monotone", dataKey: s.label, stroke: colorAt(i), strokeWidth: 2, fill: colorAt(i), fillOpacity: 0.18 }, s.label)))] }));
156
+ }
157
+ return (_jsxs("div", { ref: rootRef, className: `appkit-chart ${className}`.trim(), role: "group", "aria-label": accessibleName, children: [multi && palette ? (_jsx("div", { className: "appkit-chart__legend", children: spec.series.map((s, i) => (_jsxs("span", { children: [_jsx("span", { className: "appkit-chart__legend-swatch", style: { background: colorAt(i) }, "aria-hidden": "true" }), s.label] }, s.label))) })) : null, showTable ? (renderTable()) : (_jsx("div", { className: "appkit-chart__plot", style: { height }, children: palette ? (_jsx(ResponsiveContainer, { width: "100%", height: "100%", children: plot })) : null })), spec.caption ? _jsx("p", { className: "appkit-chart__caption", children: spec.caption }) : null, hideTableToggle ? null : (_jsx("div", { className: "appkit-chart__toolbar", children: _jsx("button", { type: "button", className: "appkit-chart__toggle", onClick: () => setShowTable((v) => !v), "aria-pressed": showTable, children: showTable ? "Show chart" : "Show as table" }) }))] }));
158
+ }
159
+ /**
160
+ * Render a `ChartSpec` from untrusted JSON, showing the validation reason when
161
+ * it does not parse — never rendering nothing.
162
+ */
163
+ export function ChartFromJson({ json, ...rest }) {
164
+ const result = useMemo(() => parseChartAttr(json), [json]);
165
+ if (!result.ok) {
166
+ return (_jsx("div", { className: "appkit-chart", children: _jsxs("p", { className: "appkit-chart__error", children: ["Chart could not be rendered: ", result.reason] }) }));
167
+ }
168
+ return _jsx(Chart, { spec: result.spec, ...rest });
169
+ }
@@ -0,0 +1,214 @@
1
+ /*
2
+ * Chart palette + chrome.
3
+ *
4
+ * ── 🛑 The custom properties are scoped to `.appkit-chart`, NOT `:root` ────
5
+ *
6
+ * Two reasons, and the second is the load-bearing one:
7
+ *
8
+ * 1. A chart palette is not a theme token. Nothing outside a chart should be
9
+ * able to consume `--chart-3`, and putting it on `:root` invites exactly
10
+ * that.
11
+ * 2. `audit-theme-drift` matches `:root { ... }` blocks in the token layer
12
+ * against the TypeScript theme presets, so a NEW `:root` custom property
13
+ * declared here is a drift finding waiting to happen. Scoping to the
14
+ * component sidesteps the question entirely.
15
+ *
16
+ * Reading them off the chart's OWN element (rather than `documentElement`) is
17
+ * also what makes the cascade do the work: a `[data-theme]` override below
18
+ * resolves correctly with no JS mode detection.
19
+ *
20
+ * ── 🛑 The dark steps are SELECTED, not flipped ───────────────────────────
21
+ *
22
+ * Both palettes were run through the dataviz validator against the real app
23
+ * surfaces (#ffffff and #0f172a). The light set is the reference order. The
24
+ * dark set keeps that hue ORDER — because the order is what the adjacent-pair
25
+ * CVD check passes on — and re-steps only the four hues that fell outside the
26
+ * dark lightness band (L 0.48–0.67).
27
+ *
28
+ * Re-ordering instead was tried and was much worse: moving pink next to green
29
+ * produced an adjacent deuteranopia ΔE of 1.7, i.e. two series a red-green
30
+ * colourblind reader cannot tell apart at all.
31
+ *
32
+ * Light set carries a sub-3:1 contrast WARN on --chart-3/4/5. That is not
33
+ * dismissable: the component always renders a legend for ≥2 series and offers
34
+ * a table view, which is the required relief.
35
+ *
36
+ * If you change ANY value here, re-run:
37
+ * node scripts/validate_palette.js "<the 8 hexes>" --mode light --surface "#ffffff"
38
+ * node scripts/validate_palette.js "<the 8 hexes>" --mode dark --surface "#0f172a"
39
+ */
40
+
41
+ .appkit-chart {
42
+ --chart-1: #2a78d6;
43
+ --chart-2: #eb6834;
44
+ --chart-3: #1baf7a;
45
+ --chart-4: #eda100;
46
+ --chart-5: #e87ba4;
47
+ --chart-6: #008300;
48
+ --chart-7: #4a3aa7;
49
+ --chart-8: #e34948;
50
+
51
+ --chart-grid: rgba(113, 113, 122, 0.22);
52
+ --chart-axis: rgba(113, 113, 122, 0.55);
53
+ --chart-ink: var(--appkit-color-text, #18181b);
54
+ --chart-ink-muted: var(--appkit-color-text-muted, #52525b);
55
+ --chart-tooltip-bg: var(--appkit-color-surface-elevated, #ffffff);
56
+ --chart-tooltip-border: var(--appkit-color-border, rgba(113, 113, 122, 0.3));
57
+
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: 0.75rem;
61
+ margin: 1.5rem 0;
62
+ }
63
+
64
+ @media (prefers-color-scheme: dark) {
65
+ .appkit-chart {
66
+ --chart-2: #E36430;
67
+ --chart-4: #C38600;
68
+ --chart-5: #D76691;
69
+ --chart-7: #6655CE;
70
+ --chart-grid: rgba(161, 161, 170, 0.2);
71
+ --chart-axis: rgba(161, 161, 170, 0.5);
72
+ }
73
+ }
74
+
75
+ /*
76
+ * The viewer's explicit toggle must win over the media query in BOTH
77
+ * directions — `applyTheme` stamps `data-theme="<theme id>"` on <html>, and a
78
+ * light theme chosen on a dark-OS machine has to restore the light steps.
79
+ * `:root[data-theme]` is deliberately broad here: the attribute carries a theme
80
+ * ID (e.g. "default-light"), not the literal mode, so the mode-specific
81
+ * selectors below key off the id prefix the built-in themes use.
82
+ */
83
+ :root[data-theme*="dark"] .appkit-chart {
84
+ --chart-2: #E36430;
85
+ --chart-4: #C38600;
86
+ --chart-5: #D76691;
87
+ --chart-7: #6655CE;
88
+ --chart-grid: rgba(161, 161, 170, 0.2);
89
+ --chart-axis: rgba(161, 161, 170, 0.5);
90
+ }
91
+
92
+ :root[data-theme*="light"] .appkit-chart {
93
+ --chart-2: #eb6834;
94
+ --chart-4: #eda100;
95
+ --chart-5: #e87ba4;
96
+ --chart-7: #4a3aa7;
97
+ --chart-grid: rgba(113, 113, 122, 0.22);
98
+ --chart-axis: rgba(113, 113, 122, 0.55);
99
+ }
100
+
101
+ .appkit-chart__caption {
102
+ font-size: 0.8125rem;
103
+ line-height: 1.5;
104
+ color: var(--chart-ink-muted);
105
+ margin: 0;
106
+ }
107
+
108
+ .appkit-chart__legend {
109
+ display: flex;
110
+ flex-wrap: wrap;
111
+ gap: 0.5rem 1.25rem;
112
+ align-items: center;
113
+ font-size: 0.75rem;
114
+ color: var(--chart-ink-muted);
115
+ }
116
+
117
+ .appkit-chart__legend-swatch {
118
+ display: inline-block;
119
+ width: 11px;
120
+ height: 11px;
121
+ border-radius: 2px;
122
+ margin-right: 0.4rem;
123
+ vertical-align: -1px;
124
+ flex: 0 0 auto;
125
+ }
126
+
127
+ .appkit-chart__plot {
128
+ width: 100%;
129
+ }
130
+
131
+ /* Wide charts scroll in their own container so the page body never does. */
132
+ .appkit-chart__scroll {
133
+ overflow-x: auto;
134
+ width: 100%;
135
+ }
136
+
137
+ .appkit-chart__toolbar {
138
+ display: flex;
139
+ justify-content: flex-end;
140
+ }
141
+
142
+ .appkit-chart__toggle {
143
+ font: inherit;
144
+ font-size: 0.6875rem;
145
+ letter-spacing: 0.04em;
146
+ text-transform: uppercase;
147
+ color: var(--chart-ink-muted);
148
+ background: none;
149
+ border: 1px solid var(--chart-tooltip-border);
150
+ border-radius: 3px;
151
+ padding: 0.2rem 0.5rem;
152
+ cursor: pointer;
153
+ }
154
+
155
+ .appkit-chart__toggle:hover {
156
+ color: var(--chart-ink);
157
+ }
158
+
159
+ .appkit-chart__toggle:focus-visible {
160
+ outline: 2px solid var(--appkit-color-focus-ring, #2a78d6);
161
+ outline-offset: 2px;
162
+ }
163
+
164
+ .appkit-chart__table {
165
+ border-collapse: collapse;
166
+ width: 100%;
167
+ font-size: 0.8125rem;
168
+ font-variant-numeric: tabular-nums;
169
+ color: var(--chart-ink);
170
+ }
171
+
172
+ .appkit-chart__table th,
173
+ .appkit-chart__table td {
174
+ border-bottom: 1px solid var(--chart-grid);
175
+ padding: 0.4rem 0.6rem;
176
+ text-align: left;
177
+ }
178
+
179
+ .appkit-chart__table td + td,
180
+ .appkit-chart__table th + th {
181
+ text-align: right;
182
+ white-space: nowrap;
183
+ }
184
+
185
+ .appkit-chart__tooltip {
186
+ background: var(--chart-tooltip-bg);
187
+ border: 1px solid var(--chart-tooltip-border);
188
+ border-radius: 4px;
189
+ padding: 0.5rem 0.65rem;
190
+ font-size: 0.75rem;
191
+ color: var(--chart-ink);
192
+ font-variant-numeric: tabular-nums;
193
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
194
+ }
195
+
196
+ .appkit-chart__tooltip-label {
197
+ font-weight: 600;
198
+ margin-bottom: 0.25rem;
199
+ color: var(--chart-ink);
200
+ }
201
+
202
+ .appkit-chart__tooltip-row {
203
+ display: flex;
204
+ gap: 0.75rem;
205
+ justify-content: space-between;
206
+ }
207
+
208
+ .appkit-chart__error {
209
+ font-size: 0.8125rem;
210
+ color: var(--appkit-color-error, #e34948);
211
+ border: 1px solid var(--appkit-color-error, #e34948);
212
+ border-radius: 4px;
213
+ padding: 0.6rem 0.75rem;
214
+ }
@@ -1,6 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from "react";
3
- import { Div, Stack, Text } from "../index.js";
3
+ /*
4
+ * Defining modules, not `../index` — Root Cause #18.
5
+ *
6
+ * The barrel re-exports THIS component, so importing three primitives back
7
+ * through it closed a two-module cycle (`audit-import-cycles` component 1).
8
+ * The deep paths below have one direction only.
9
+ */
10
+ import { Div } from "./Div.js";
11
+ import { Stack } from "./Layout.js";
12
+ import { Text } from "./Typography.js";
4
13
  import { DISCLAIMERS, } from "../../_internal/shared/content/disclaimers.js";
5
14
  import { resolvePublishedCopy, resolvePublishedValues, } from "../../_internal/shared/content/published-values.js";
6
15
  /**
@@ -26,14 +26,22 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
26
26
  * @tag sideEffects:dom-measurement
27
27
  */
28
28
  import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
29
+ import { createPortal } from "react-dom";
29
30
  import NextLink from "next/link.js";
30
31
  import { Check, ChevronDown, Search } from "lucide-react";
31
32
  import { Avatar } from "./Avatar.js";
32
33
  import { Icon } from "./Icon.js";
33
34
  import { Input } from "./Input.js";
34
35
  import { useRowCollapse } from "./row-collapse.js";
36
+ import { MOBILE_QUERY } from "./toolbar-collapse.js";
37
+ import { useBottomChromeSlot } from "./bottom-chrome-slot.js";
38
+ import { useMediaQuery } from "../../react/hooks/useMediaQuery.js";
35
39
  const UI_OVERFLOW_ROW = {
36
40
  shell: "appkit-overflow-row",
41
+ /** The in-flow measuring box, once the control itself has been portalled away. */
42
+ shellDocked: "appkit-overflow-row--docked",
43
+ /** The portalled control, as its own row inside `BottomChrome`. */
44
+ dock: "appkit-overflow-row__dock",
37
45
  row: "appkit-overflow-row__row",
38
46
  pill: "appkit-overflow-row__pill",
39
47
  pillActive: "appkit-overflow-row__pill--active",
@@ -77,11 +85,22 @@ export function OverflowRow({ items, ariaLabel, activeId, variant = "tabs", plac
77
85
  const shellRef = useRef(null);
78
86
  const rowRef = useRef(null);
79
87
  const triggerRef = useRef(null);
88
+ /*
89
+ * The portalled control, when docked into the bottom bar. Needed only by the
90
+ * outside-click handler: a portal moves the node out of `shellRef`'s subtree
91
+ * in the DOM, so `shell.contains(target)` is FALSE for a click on the
92
+ * trigger itself — the dropdown would close on the very click that opened
93
+ * it, which reads as the control being dead.
94
+ */
95
+ const dockRef = useRef(null);
80
96
  const searchRef = useRef(null);
81
97
  const listboxId = useId();
82
98
  const [open, setOpen] = useState(false);
83
99
  const [query, setQuery] = useState("");
84
100
  const [activeIndex, setActiveIndex] = useState(0);
101
+ // Both before any conditional return — the collapsed branch is one.
102
+ const bottomChromeSlot = useBottomChromeSlot();
103
+ const isMobile = useMediaQuery(MOBILE_QUERY);
85
104
  const signature = useMemo(() => signatureOf(items), [items]);
86
105
  const collapsed = useRowCollapse(shellRef, rowRef, signature, {
87
106
  disabled: alwaysCollapsed,
@@ -92,7 +111,11 @@ export function OverflowRow({ items, ariaLabel, activeId, variant = "tabs", plac
92
111
  if (!open)
93
112
  return;
94
113
  const onDown = (event) => {
95
- if (!shellRef.current?.contains(event.target))
114
+ const target = event.target;
115
+ const inShell = shellRef.current?.contains(target) ?? false;
116
+ // See `dockRef` — when docked, the control lives outside the shell.
117
+ const inDock = dockRef.current?.contains(target) ?? false;
118
+ if (!inShell && !inDock)
96
119
  setOpen(false);
97
120
  };
98
121
  document.addEventListener("mousedown", onDown);
@@ -166,7 +189,7 @@ export function OverflowRow({ items, ariaLabel, activeId, variant = "tabs", plac
166
189
  const hasAnyVisual = items.some((item) => item.imageUrl || item.iconName || item.emoji || item.dotTone);
167
190
  // ── Collapsed: one searchable dropdown ──────────────────────────────────
168
191
  if (collapsed) {
169
- return (_jsxs("div", { ref: shellRef, className: [UI_OVERFLOW_ROW.shell, className].filter(Boolean).join(" "), "data-collapsed": "true", children: [_jsxs("button", { ref: triggerRef, type: "button", className: UI_OVERFLOW_ROW.trigger, "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-label": ariaLabel, onClick: () => setOpen((prev) => !prev), onKeyDown: (event) => {
192
+ const dropdown = (_jsxs(_Fragment, { children: [_jsxs("button", { ref: triggerRef, type: "button", className: UI_OVERFLOW_ROW.trigger, "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-label": ariaLabel, onClick: () => setOpen((prev) => !prev), onKeyDown: (event) => {
170
193
  if (!open && (event.key === "ArrowDown" || event.key === "Enter")) {
171
194
  event.preventDefault();
172
195
  setOpen(true);
@@ -186,6 +209,35 @@ export function OverflowRow({ items, ariaLabel, activeId, variant = "tabs", plac
186
209
  const body = (_jsxs(_Fragment, { children: [hasAnyVisual ? visualOf(item, "option") : null, _jsx("span", { className: UI_OVERFLOW_ROW.optionLabel, children: item.label }), typeof item.count === "number" && (_jsx("span", { className: UI_OVERFLOW_ROW.optionCount, children: item.count > 99 ? "99+" : item.count })), isSelected && (_jsx(Check, { className: `w-4 h-4 ${UI_OVERFLOW_ROW.check}`, "aria-hidden": "true" }))] }));
187
210
  return item.href ? (_jsx(Link, { ...linkProps, id: `${listboxId}-${item.id}`, role: "option", "aria-selected": isSelected, href: item.href, className: classes, onMouseEnter: () => setActiveIndex(index), children: body }, item.id)) : (_jsx("button", { id: `${listboxId}-${item.id}`, role: "option", type: "button", "aria-selected": isSelected, disabled: item.disabled, className: classes, onClick: () => commit(item), onMouseEnter: () => setActiveIndex(index), children: body }, item.id));
188
211
  })] })] }))] }));
212
+ /*
213
+ * ── Mobile: the collapsed row docks into the bottom bar ────────────────
214
+ *
215
+ * A row that has collapsed to a dropdown is no longer a row — it is one
216
+ * control, and on a phone the place for one control is the thumb zone,
217
+ * not a band competing with the page heading. It joins `BottomChrome` as
218
+ * its OWN row, so it stacks above a CTA bar instead of overlapping it,
219
+ * and the tier's single `ResizeObserver` adds its height to
220
+ * `--bottom-chrome-height` — which is what keeps page content from being
221
+ * hidden behind it on scroll, with no consumer doing anything.
222
+ *
223
+ * 🛑 `shellRef` STAYS IN PLACE, always. It is what `useRowCollapse`
224
+ * measures the available width against; portal it into the bottom bar and
225
+ * it would measure the VIEWPORT instead, read as "there is room now",
226
+ * expand, return to the narrow container, collapse, and oscillate forever.
227
+ * So the shell remains an empty in-flow box at the original position and
228
+ * only the CONTROL is portalled. The expanded branch below is unchanged
229
+ * and still renders the real row there.
230
+ */
231
+ const docked = isMobile && !!bottomChromeSlot;
232
+ return (_jsx("div", { ref: shellRef, className: [
233
+ UI_OVERFLOW_ROW.shell,
234
+ docked ? UI_OVERFLOW_ROW.shellDocked : "",
235
+ className,
236
+ ]
237
+ .filter(Boolean)
238
+ .join(" "), "data-collapsed": "true", "data-docked": docked ? "true" : "false", children: docked
239
+ ? createPortal(_jsx("div", { ref: dockRef, className: UI_OVERFLOW_ROW.dock, children: dropdown }), bottomChromeSlot)
240
+ : dropdown }));
189
241
  }
190
242
  // ── Expanded: the pill row ──────────────────────────────────────────────
191
243
  return (_jsx("div", { ref: shellRef, className: [UI_OVERFLOW_ROW.shell, className].filter(Boolean).join(" "), "data-collapsed": "false", children: _jsx("div", { ref: rowRef, className: UI_OVERFLOW_ROW.row, role: variant === "tabs" ? "tablist" : undefined, "aria-label": ariaLabel, children: items.map((item) => {