@burojs/ui-shadcn 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/README.md +26 -7
  2. package/dist/bulk-summary-CwSd6Yjb.cjs +108 -0
  3. package/dist/bulk-summary-CwSd6Yjb.cjs.map +1 -0
  4. package/dist/bulk-summary-GxMDP4bw.js +108 -0
  5. package/dist/bulk-summary-GxMDP4bw.js.map +1 -0
  6. package/dist/button-BxnnE28H.cjs +55 -0
  7. package/dist/button-BxnnE28H.cjs.map +1 -0
  8. package/dist/button-MWVeZaqj.js +44 -0
  9. package/dist/button-MWVeZaqj.js.map +1 -0
  10. package/dist/candidates-gate-CX2PBu6l.js +63 -0
  11. package/dist/candidates-gate-CX2PBu6l.js.map +1 -0
  12. package/dist/candidates-gate-DiFI52r7.cjs +70 -0
  13. package/dist/candidates-gate-DiFI52r7.cjs.map +1 -0
  14. package/dist/candidates-relation-picker-CBsVcnPR.js +82 -0
  15. package/dist/candidates-relation-picker-CBsVcnPR.js.map +1 -0
  16. package/dist/candidates-relation-picker-Dd_GVLlu.cjs +84 -0
  17. package/dist/candidates-relation-picker-Dd_GVLlu.cjs.map +1 -0
  18. package/dist/check-confirm-dialog-BRByTFpg.cjs +1534 -0
  19. package/dist/check-confirm-dialog-BRByTFpg.cjs.map +1 -0
  20. package/dist/check-confirm-dialog-CkaVTPT9.js +1232 -0
  21. package/dist/check-confirm-dialog-CkaVTPT9.js.map +1 -0
  22. package/dist/dialog-B_rt9S1c.js +63 -0
  23. package/dist/dialog-B_rt9S1c.js.map +1 -0
  24. package/dist/dialog-DRNUq9EA.cjs +112 -0
  25. package/dist/dialog-DRNUq9EA.cjs.map +1 -0
  26. package/dist/filter-builder-B3mZFL-K.cjs +1761 -0
  27. package/dist/filter-builder-B3mZFL-K.cjs.map +1 -0
  28. package/dist/filter-builder-DOnaXNfP.js +1605 -0
  29. package/dist/filter-builder-DOnaXNfP.js.map +1 -0
  30. package/dist/flow-runtime-B6zOHYqI.cjs +255 -0
  31. package/dist/flow-runtime-B6zOHYqI.cjs.map +1 -0
  32. package/dist/flow-runtime-D8h99FSg.js +252 -0
  33. package/dist/flow-runtime-D8h99FSg.js.map +1 -0
  34. package/dist/flow-screen-CX05b3G9.cjs +705 -0
  35. package/dist/flow-screen-CX05b3G9.cjs.map +1 -0
  36. package/dist/flow-screen-CmbARSqg.js +701 -0
  37. package/dist/flow-screen-CmbARSqg.js.map +1 -0
  38. package/dist/index.cjs +6453 -17494
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +3754 -3621
  41. package/dist/index.d.ts +3754 -3621
  42. package/dist/index.js +6217 -17219
  43. package/dist/index.js.map +1 -1
  44. package/dist/inline-flow-host-B7U1OJUe.cjs +112 -0
  45. package/dist/inline-flow-host-B7U1OJUe.cjs.map +1 -0
  46. package/dist/inline-flow-host-C3H2UpSA.js +112 -0
  47. package/dist/inline-flow-host-C3H2UpSA.js.map +1 -0
  48. package/dist/input-BI0czGyI.cjs +23 -0
  49. package/dist/input-BI0czGyI.cjs.map +1 -0
  50. package/dist/input-CUPStzZO.js +17 -0
  51. package/dist/input-CUPStzZO.js.map +1 -0
  52. package/dist/label-66W1qbke.js +29 -0
  53. package/dist/label-66W1qbke.js.map +1 -0
  54. package/dist/label-w6JYf2cF.cjs +36 -0
  55. package/dist/label-w6JYf2cF.cjs.map +1 -0
  56. package/dist/layout/theme-storage-key.cjs +5 -10
  57. package/dist/layout/theme-storage-key.cjs.map +1 -1
  58. package/dist/layout/theme-storage-key.d.cts +2 -23
  59. package/dist/layout/theme-storage-key.d.ts +2 -23
  60. package/dist/layout/theme-storage-key.js +5 -2
  61. package/dist/layout/theme-storage-key.js.map +1 -1
  62. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js +14 -0
  63. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js.map +1 -0
  64. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs +19 -0
  65. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs.map +1 -0
  66. package/dist/markdown/index.cjs +424 -511
  67. package/dist/markdown/index.cjs.map +1 -1
  68. package/dist/markdown/index.d.cts +1072 -34
  69. package/dist/markdown/index.d.ts +1072 -34
  70. package/dist/markdown/index.js +411 -459
  71. package/dist/markdown/index.js.map +1 -1
  72. package/dist/page-flow-route-CQ7HsbBm.cjs +277 -0
  73. package/dist/page-flow-route-CQ7HsbBm.cjs.map +1 -0
  74. package/dist/page-flow-route-CyXSvXSE.js +277 -0
  75. package/dist/page-flow-route-CyXSvXSE.js.map +1 -0
  76. package/dist/peek-filter-CnqHwTMU.cjs +51 -0
  77. package/dist/peek-filter-CnqHwTMU.cjs.map +1 -0
  78. package/dist/peek-filter-ErLfO153.js +28 -0
  79. package/dist/peek-filter-ErLfO153.js.map +1 -0
  80. package/dist/place-node--GdsP688.js +7315 -0
  81. package/dist/place-node--GdsP688.js.map +1 -0
  82. package/dist/place-node-SIp4XYQH.cjs +7833 -0
  83. package/dist/place-node-SIp4XYQH.cjs.map +1 -0
  84. package/dist/related-list-cards-B6x9HQQp.js +42 -0
  85. package/dist/related-list-cards-B6x9HQQp.js.map +1 -0
  86. package/dist/related-list-cards-nxuODlIA.cjs +42 -0
  87. package/dist/related-list-cards-nxuODlIA.cjs.map +1 -0
  88. package/dist/related-list-timeline-BYJKEbmA.cjs +142 -0
  89. package/dist/related-list-timeline-BYJKEbmA.cjs.map +1 -0
  90. package/dist/related-list-timeline-DEdfd-rr.js +142 -0
  91. package/dist/related-list-timeline-DEdfd-rr.js.map +1 -0
  92. package/dist/related-rows-B4levHcR.js +186 -0
  93. package/dist/related-rows-B4levHcR.js.map +1 -0
  94. package/dist/related-rows-zh5f_4QR.cjs +209 -0
  95. package/dist/related-rows-zh5f_4QR.cjs.map +1 -0
  96. package/dist/relation-create-BOaUe12G.cjs +362 -0
  97. package/dist/relation-create-BOaUe12G.cjs.map +1 -0
  98. package/dist/relation-create-Bg-eEJLs.js +337 -0
  99. package/dist/relation-create-Bg-eEJLs.js.map +1 -0
  100. package/dist/relation-many-picker-B-NCEUgX.cjs +465 -0
  101. package/dist/relation-many-picker-B-NCEUgX.cjs.map +1 -0
  102. package/dist/relation-many-picker-gdAOD-qK.js +464 -0
  103. package/dist/relation-many-picker-gdAOD-qK.js.map +1 -0
  104. package/dist/relation-picker-BAY77ivO.js +462 -0
  105. package/dist/relation-picker-BAY77ivO.js.map +1 -0
  106. package/dist/relation-picker-Da_uhFvA.cjs +479 -0
  107. package/dist/relation-picker-Da_uhFvA.cjs.map +1 -0
  108. package/dist/relation-picker-dialog-M0pQaWlp.cjs +300 -0
  109. package/dist/relation-picker-dialog-M0pQaWlp.cjs.map +1 -0
  110. package/dist/relation-picker-dialog-hA9rYrZB.js +299 -0
  111. package/dist/relation-picker-dialog-hA9rYrZB.js.map +1 -0
  112. package/dist/resource-page-router-OSQyRA5b.cjs +1779 -0
  113. package/dist/resource-page-router-OSQyRA5b.cjs.map +1 -0
  114. package/dist/resource-page-router-T4vS5gbJ.js +1582 -0
  115. package/dist/resource-page-router-T4vS5gbJ.js.map +1 -0
  116. package/dist/rich-text-DFerywgz.js +457 -0
  117. package/dist/rich-text-DFerywgz.js.map +1 -0
  118. package/dist/rich-text-vTyVjDoa.cjs +460 -0
  119. package/dist/rich-text-vTyVjDoa.cjs.map +1 -0
  120. package/dist/search-access-B7G5oR8n.cjs +812 -0
  121. package/dist/search-access-B7G5oR8n.cjs.map +1 -0
  122. package/dist/search-access-CURsWn-U.js +669 -0
  123. package/dist/search-access-CURsWn-U.js.map +1 -0
  124. package/dist/shell-BPlk8hEa.js +205 -0
  125. package/dist/shell-BPlk8hEa.js.map +1 -0
  126. package/dist/shell-DxhlsPwm.cjs +271 -0
  127. package/dist/shell-DxhlsPwm.cjs.map +1 -0
  128. package/dist/skeleton-U2noefj5.js +13 -0
  129. package/dist/skeleton-U2noefj5.js.map +1 -0
  130. package/dist/skeleton-_1gP7wan.cjs +18 -0
  131. package/dist/skeleton-_1gP7wan.cjs.map +1 -0
  132. package/dist/styles.css +255 -17
  133. package/dist/theme/theme-css.cjs +95 -96
  134. package/dist/theme/theme-css.cjs.map +1 -1
  135. package/dist/theme/theme-css.d.cts +10 -8
  136. package/dist/theme/theme-css.d.ts +10 -8
  137. package/dist/theme/theme-css.js +94 -94
  138. package/dist/theme/theme-css.js.map +1 -1
  139. package/dist/theme/theme-storage.cjs +34 -39
  140. package/dist/theme/theme-storage.cjs.map +1 -1
  141. package/dist/theme/theme-storage.d.cts +3 -1
  142. package/dist/theme/theme-storage.d.ts +3 -1
  143. package/dist/theme/theme-storage.js +33 -37
  144. package/dist/theme/theme-storage.js.map +1 -1
  145. package/dist/theme-storage-key-pYlp4aDS.d.cts +31 -0
  146. package/dist/theme-storage-key-pYlp4aDS.d.ts +31 -0
  147. package/dist/toaster-C7CiYA4Z.cjs +248 -0
  148. package/dist/toaster-C7CiYA4Z.cjs.map +1 -0
  149. package/dist/toaster-CfCRLgR-.js +164 -0
  150. package/dist/toaster-CfCRLgR-.js.map +1 -0
  151. package/dist/use-dialog-filter-state-BBCC0OEK.cjs +92 -0
  152. package/dist/use-dialog-filter-state-BBCC0OEK.cjs.map +1 -0
  153. package/dist/use-dialog-filter-state-BDcgS5Oz.js +87 -0
  154. package/dist/use-dialog-filter-state-BDcgS5Oz.js.map +1 -0
  155. package/dist/use-theme-DFlpXils.cjs +257 -0
  156. package/dist/use-theme-DFlpXils.cjs.map +1 -0
  157. package/dist/use-theme-dnE3Od3I.js +175 -0
  158. package/dist/use-theme-dnE3Od3I.js.map +1 -0
  159. package/package.json +18 -17
  160. package/dist/chunk-ABUQPOOE.js +0 -11
  161. package/dist/chunk-ABUQPOOE.js.map +0 -1
  162. package/dist/chunk-DBGGWBFG.cjs +0 -199
  163. package/dist/chunk-DBGGWBFG.cjs.map +0 -1
  164. package/dist/chunk-FMX4GLCQ.js +0 -6
  165. package/dist/chunk-FMX4GLCQ.js.map +0 -1
  166. package/dist/chunk-UC57Z243.js +0 -143
  167. package/dist/chunk-UC57Z243.js.map +0 -1
  168. package/dist/chunk-UPGPO75B.js +0 -164
  169. package/dist/chunk-UPGPO75B.js.map +0 -1
  170. package/dist/chunk-VXJAXJ3U.cjs +0 -8
  171. package/dist/chunk-VXJAXJ3U.cjs.map +0 -1
  172. package/dist/chunk-W7AEYE7A.cjs +0 -13
  173. package/dist/chunk-W7AEYE7A.cjs.map +0 -1
  174. package/dist/chunk-YF5IW525.cjs +0 -155
  175. package/dist/chunk-YF5IW525.cjs.map +0 -1
  176. package/dist/rich-text-6V7WZ3WV.cjs +0 -518
  177. package/dist/rich-text-6V7WZ3WV.cjs.map +0 -1
  178. package/dist/rich-text-N2FZEJJV.js +0 -491
  179. package/dist/rich-text-N2FZEJJV.js.map +0 -1
package/README.md CHANGED
@@ -46,18 +46,37 @@ build plugin for your bundler, installed as its own devDependency:
46
46
 
47
47
  **→ [docs/theming.md](../../docs/theming.md)** has the full setup, how to
48
48
  override a token, and the reasoning behind the base-layer rules and the
49
- `--destructive` colours. `apps/playground` (Next) and `examples/vite`
50
- (Vite) are working examples to copy from.
49
+ `--destructive` colours. `examples/vite` (Vite) and `examples/next` (Next)
50
+ are the working examples to copy from. (This line named `apps/playground` as
51
+ the Next example. It was wrong twice over: that application had been a Vite
52
+ app since 2026-07-31, and SP9 has now deleted it.)
53
+
54
+ ## Router setup
55
+
56
+ Mount your router's `RouterAdapterProvider` (`@burojs/react`) above
57
+ `<BuroProvider>`: it is the router's only binding, and every route match, active
58
+ nav item, breadcrumb and framework navigation goes through it. Then give
59
+ `<LinkComponentProvider linkComponent={…}>` your router's link component, which
60
+ only renders anchors. `RouterAdapterProvider`'s docblock has the react-router and
61
+ Next App Router adapters; `examples/vite` and `examples/next` mount them.
62
+
63
+ ## App shell
64
+
65
+ **→ [docs/app-shell-guide.md](../../docs/app-shell-guide.md)** covers
66
+ `AppShell`, the ready-made application chrome, and how to replace any part of it.
51
67
 
52
68
  ## What's included
53
69
 
54
- - Layout: AppLayout, Sidebar, Breadcrumbs
70
+ - Layout: AppLayout, AppSidebar and AppHeader (each slot you omit gets its default — brand, `<NavMain />` and `<UserMenu />`; `<HeaderBreadcrumbs />`, the palette chip, app actions and refresh — and `false` empties it), Sidebar, Breadcrumbs, HeaderBreadcrumbs (the header trail of the current route)
55
71
  - Auth: LoginForm
56
- - Resource views: ResourceList, ResourceShow, ResourceEdit, ResourceCreate
72
+ - Resource views: ResourceList, and `<RecordScreen resource mode recordId? …>` — a record's show, edit or create screen outside a route, in the built-in frame or in your own layout of its pieces (`RecordTitle`, `RecordActions`, `CopyId`, `FormActions`)
73
+ - Record pages: `<RecordPage resource action />` (plus `defaults` on `action="create"`) — show, edit and create wired to data (`useOne`, `useUpdateOne`, `useCreateOne`), with translated loading / not-found / session-expired / error states and the open panel of a keyed tabs node in the URL (`?<tabsKey>=<panelKey>`); `<ResourcePageRouter>` / `<NavAreaPage>` render it for every record route (a different layout is a frame on the screen, `views.<mode>.frame`; record overrides were removed in P6); the page draws no breadcrumb trail of its own: `<HeaderBreadcrumbs />` in `<AppHeader breadcrumbs>` draws the record trail there (`<RecordBreadcrumbs />` standalone), with the record's title once the page has loaded it and the id while it loads or has failed, and on a page-hosted flow's route ending with the flow's label; each show and edit visit is recorded under the signed-in user for the command palette's Recent group
74
+ - Custom pages: a page declared with a `body` (`defineCustomPage({ …, body: (l) => [l.place(block, { key })] })`) renders on its route under its label as the `<h1>`, behind the page's and its groups' `visible`; a page without `body` is a nav entry over a route the host owns
57
75
  - Field renderers: text, number, boolean, date, enum, textarea (display + form input)
58
- - Actions: ActionButton, BulkActionToolbar (wrap @burojs/react useAction)
59
- - Notifications: Toaster + NotificationProvider (Sonner)
60
- - UI presentation hosts: dialog/sheet chrome for the framework-mounted `<UIPresentations/>` overlay stack (`shadcnPresentationHosts`), backing `modal`/`formModal` actions
76
+ - Actions: ActionButton (links, modals and flows), FlowActionButton, AppFlowButton, BulkActionToolbar (bulk flows); `useAction` from `@burojs/react/flows` runs a flow from your own control
77
+ - Notifications: Toaster (Sonner) — render it inside `<BuroProvider>`, where it registers itself as the dispatcher's notification sink
78
+ - Unsaved-changes dialog: `<NavigationGuardDialog>`, registered by `<AppLayout>` (the guard itself is `<BuroProvider>`'s)
79
+ - UI presentation hosts: dialog/sheet chrome for the framework-mounted `<UIPresentations/>` overlay stack (`shadcnPresentationHosts`), backing `modal` actions and the flow host (`screens.flow`, the `buro:flow` dialog or sheet)
61
80
  - Vendored shadcn primitives: Button, Input, Label, Form, Table, Card, Dialog, Sheet, Select, Checkbox, Textarea, Badge
62
81
 
63
82
  ## License
@@ -0,0 +1,108 @@
1
+ const require_button = require("./button-BxnnE28H.cjs");
2
+ let _burojs_core = require("@burojs/core");
3
+ let _burojs_react = require("@burojs/react");
4
+ let react = require("react");
5
+ let react_jsx_runtime = require("react/jsx-runtime");
6
+ let _burojs_react_flows = require("@burojs/react/flows");
7
+ //#region src/flows/bulk-summary.tsx
8
+ const text = (t, key, fallback, params) => {
9
+ const value = t(key, params === void 0 ? void 0 : { params });
10
+ return value === key ? fallback : value;
11
+ };
12
+ function FlowBulkSummary({ resource, actionName, label, total, progress }) {
13
+ const t = (0, _burojs_react.useOptionalT)();
14
+ const caps = (0, _burojs_react.useI18nCaps)(resource);
15
+ const forget = (0, _burojs_react_flows.useForgetEachAttempt)({
16
+ resource,
17
+ action: actionName
18
+ });
19
+ const [forgotten, setForgotten] = (0, react.useState)(() => new Set());
20
+ const forgetText = text(t, "flow.forget", "I've checked — forget it");
21
+ const testid = (0, _burojs_react.useTestIds)().bulkSummary(resource, actionName);
22
+ const own = `resources.${resource}.actions.${actionName}.summary`;
23
+ const params = {
24
+ count: progress.success.length,
25
+ total
26
+ };
27
+ const heading = caps.has(own) ? caps.t(own, { params }) : `${label}: ${text(t, "flow.bulkSummary", `${progress.success.length} processed`, params)} / ${total}`;
28
+ const notProcessed = progress.rejected.filter((entry) => entry.reason !== "server");
29
+ const refused = progress.rejected.filter((entry) => entry.reason === "server");
30
+ const groups = [
31
+ {
32
+ key: "not-processed",
33
+ title: text(t, "flow.rejected.precondition", "The record changed. Nothing was sent."),
34
+ ids: notProcessed.map((entry) => entry.id)
35
+ },
36
+ {
37
+ key: "rejected",
38
+ title: text(t, "flow.rejected.validation", "The server refused the values."),
39
+ ids: refused.map((entry) => entry.id)
40
+ },
41
+ {
42
+ key: "failed",
43
+ title: text(t, "flow.failed", "The request could not be sent."),
44
+ ids: progress.failed
45
+ },
46
+ {
47
+ key: "unknown",
48
+ title: text(t, "flow.unknown", "It may have been applied. Nothing was resent."),
49
+ ids: progress.unknown.filter((id) => !forgotten.has(id))
50
+ },
51
+ {
52
+ key: "unresolved",
53
+ title: text(t, "flow.bulkUnresolved", "Skipped: an earlier request for it may have been applied."),
54
+ ids: progress.unresolved.filter((id) => !forgotten.has(id))
55
+ },
56
+ {
57
+ key: "not-attempted",
58
+ title: text(t, "flow.bulkNotAttempted", "Not attempted."),
59
+ ids: progress.notAttempted
60
+ }
61
+ ];
62
+ return (0, react_jsx_runtime.jsxs)("div", {
63
+ "data-testid": testid,
64
+ className: "flex flex-col gap-3 text-sm",
65
+ children: [(0, react_jsx_runtime.jsx)("p", {
66
+ "data-testid": `${testid}:heading`,
67
+ className: "font-medium",
68
+ children: heading
69
+ }), groups.filter((group) => group.ids.length > 0).map((group) => (0, react_jsx_runtime.jsxs)("section", {
70
+ "data-testid": `${testid}:${group.key}`,
71
+ children: [(0, react_jsx_runtime.jsxs)("h3", {
72
+ className: "font-medium",
73
+ children: [
74
+ group.title,
75
+ " ",
76
+ (0, react_jsx_runtime.jsxs)("span", {
77
+ "data-testid": `${testid}:${group.key}:count`,
78
+ className: "tabular-nums",
79
+ children: [
80
+ "(",
81
+ group.ids.length,
82
+ ")"
83
+ ]
84
+ })
85
+ ]
86
+ }), (0, react_jsx_runtime.jsxs)("ul", {
87
+ className: "flex flex-wrap gap-1 text-muted-foreground",
88
+ children: [group.ids.slice(0, _burojs_core.BULK_SUMMARY_IDS).map((id) => (0, react_jsx_runtime.jsxs)("li", {
89
+ "data-testid": `${testid}:${group.key}:${id}`,
90
+ className: "flex items-center gap-1",
91
+ children: [id, group.key === "unknown" || group.key === "unresolved" ? (0, react_jsx_runtime.jsx)(require_button.Button, {
92
+ type: "button",
93
+ size: "sm",
94
+ variant: "ghost",
95
+ className: "h-6 px-1.5 text-xs",
96
+ "data-testid": `${testid}:forget:${id}`,
97
+ onClick: () => void forget(id).then(() => setForgotten((previous) => new Set([...previous, id]))),
98
+ children: forgetText
99
+ }) : null]
100
+ }, id)), group.ids.length > _burojs_core.BULK_SUMMARY_IDS ? (0, react_jsx_runtime.jsxs)("li", { children: ["+", group.ids.length - _burojs_core.BULK_SUMMARY_IDS] }) : null]
101
+ })]
102
+ }, group.key))]
103
+ });
104
+ }
105
+ //#endregion
106
+ exports.FlowBulkSummary = FlowBulkSummary;
107
+
108
+ //# sourceMappingURL=bulk-summary-CwSd6Yjb.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-summary-CwSd6Yjb.cjs","names":["useOptionalT","useI18nCaps","useForgetEachAttempt","useState","useTestIds","BULK_SUMMARY_IDS","Button"],"sources":["../src/flows/bulk-summary.tsx"],"sourcesContent":["'use client';\nimport { BULK_SUMMARY_IDS, type BulkProgress } from '@burojs/core';\nimport { useI18nCaps, useOptionalT, useTestIds } from '@burojs/react';\nimport { useForgetEachAttempt } from '@burojs/react/flows';\nimport { type ReactElement, useState } from 'react';\nimport { Button } from '../primitives/button.js';\n\nexport interface FlowBulkSummaryProps {\n readonly resource: string;\n readonly actionName: string;\n /** The action's label. */\n readonly label: string;\n /** How many ids the run was given. */\n readonly total: number;\n readonly progress: BulkProgress;\n}\n\ntype Translate = ReturnType<typeof useOptionalT>;\nconst text = (t: Translate, key: string, fallback: string, params?: Record<string, unknown>) => {\n const value = t(key, params === undefined ? undefined : { params });\n return value === key ? fallback : value;\n};\n\n/**\n * A bulk flow's summary (P4 spec 4.16): the count that succeeded of the ids given (the flow's\n * own `resources.<r>.actions.<name>.summary`, `{{count}}` of `{{total}}`, else the framework's\n * count text), then one group per other outcome with its count and up to `BULK_SUMMARY_IDS` ids —\n * not processed (gone or its precondition failed), refused, failed, unknown (may have been\n * applied), unresolved from an earlier run, not attempted.\n */\nexport function FlowBulkSummary({\n resource,\n actionName,\n label,\n total,\n progress,\n}: FlowBulkSummaryProps): ReactElement {\n const t = useOptionalT();\n const caps = useI18nCaps(resource);\n // An id the run left unknown, or found unresolved, has its exit here (P4-C review I3): the user\n // checked the record, and the id's attempt is forgotten — the next run sends it again.\n const forget = useForgetEachAttempt({ resource, action: actionName });\n const [forgotten, setForgotten] = useState<ReadonlySet<string>>(() => new Set());\n const forgetText = text(t, 'flow.forget', \"I've checked — forget it\");\n const tid = useTestIds();\n const testid = tid.bulkSummary(resource, actionName);\n const own = `resources.${resource}.actions.${actionName}.summary`;\n const params = { count: progress.success.length, total };\n const heading = caps.has(own)\n ? caps.t(own, { params })\n : `${label}: ${text(t, 'flow.bulkSummary', `${progress.success.length} processed`, params)} / ${total}`;\n const notProcessed = progress.rejected.filter((entry) => entry.reason !== 'server');\n const refused = progress.rejected.filter((entry) => entry.reason === 'server');\n const groups: readonly { key: string; title: string; ids: readonly string[] }[] = [\n {\n key: 'not-processed',\n title: text(t, 'flow.rejected.precondition', 'The record changed. Nothing was sent.'),\n ids: notProcessed.map((entry) => entry.id),\n },\n {\n key: 'rejected',\n title: text(t, 'flow.rejected.validation', 'The server refused the values.'),\n ids: refused.map((entry) => entry.id),\n },\n {\n key: 'failed',\n title: text(t, 'flow.failed', 'The request could not be sent.'),\n ids: progress.failed,\n },\n {\n key: 'unknown',\n title: text(t, 'flow.unknown', 'It may have been applied. Nothing was resent.'),\n ids: progress.unknown.filter((id) => !forgotten.has(id)),\n },\n {\n key: 'unresolved',\n title: text(\n t,\n 'flow.bulkUnresolved',\n 'Skipped: an earlier request for it may have been applied.',\n ),\n ids: progress.unresolved.filter((id) => !forgotten.has(id)),\n },\n {\n key: 'not-attempted',\n title: text(t, 'flow.bulkNotAttempted', 'Not attempted.'),\n ids: progress.notAttempted,\n },\n ];\n return (\n <div data-testid={testid} className=\"flex flex-col gap-3 text-sm\">\n <p data-testid={`${testid}:heading`} className=\"font-medium\">\n {heading}\n </p>\n {groups\n .filter((group) => group.ids.length > 0)\n .map((group) => (\n <section key={group.key} data-testid={`${testid}:${group.key}`}>\n <h3 className=\"font-medium\">\n {group.title}{' '}\n <span data-testid={`${testid}:${group.key}:count`} className=\"tabular-nums\">\n ({group.ids.length})\n </span>\n </h3>\n <ul className=\"flex flex-wrap gap-1 text-muted-foreground\">\n {group.ids.slice(0, BULK_SUMMARY_IDS).map((id) => (\n <li\n key={id}\n data-testid={`${testid}:${group.key}:${id}`}\n className=\"flex items-center gap-1\"\n >\n {id}\n {group.key === 'unknown' || group.key === 'unresolved' ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-6 px-1.5 text-xs\"\n data-testid={`${testid}:forget:${id}`}\n onClick={() =>\n void forget(id).then(() =>\n setForgotten((previous) => new Set([...previous, id])),\n )\n }\n >\n {forgetText}\n </Button>\n ) : null}\n </li>\n ))}\n {group.ids.length > BULK_SUMMARY_IDS ? (\n <li>+{group.ids.length - BULK_SUMMARY_IDS}</li>\n ) : null}\n </ul>\n </section>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ import { t as Button } from "./button-MWVeZaqj.js";
2
+ import { BULK_SUMMARY_IDS } from "@burojs/core";
3
+ import { useI18nCaps, useOptionalT, useTestIds } from "@burojs/react";
4
+ import { useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { useForgetEachAttempt } from "@burojs/react/flows";
7
+ //#region src/flows/bulk-summary.tsx
8
+ const text = (t, key, fallback, params) => {
9
+ const value = t(key, params === void 0 ? void 0 : { params });
10
+ return value === key ? fallback : value;
11
+ };
12
+ function FlowBulkSummary({ resource, actionName, label, total, progress }) {
13
+ const t = useOptionalT();
14
+ const caps = useI18nCaps(resource);
15
+ const forget = useForgetEachAttempt({
16
+ resource,
17
+ action: actionName
18
+ });
19
+ const [forgotten, setForgotten] = useState(() => new Set());
20
+ const forgetText = text(t, "flow.forget", "I've checked — forget it");
21
+ const testid = useTestIds().bulkSummary(resource, actionName);
22
+ const own = `resources.${resource}.actions.${actionName}.summary`;
23
+ const params = {
24
+ count: progress.success.length,
25
+ total
26
+ };
27
+ const heading = caps.has(own) ? caps.t(own, { params }) : `${label}: ${text(t, "flow.bulkSummary", `${progress.success.length} processed`, params)} / ${total}`;
28
+ const notProcessed = progress.rejected.filter((entry) => entry.reason !== "server");
29
+ const refused = progress.rejected.filter((entry) => entry.reason === "server");
30
+ const groups = [
31
+ {
32
+ key: "not-processed",
33
+ title: text(t, "flow.rejected.precondition", "The record changed. Nothing was sent."),
34
+ ids: notProcessed.map((entry) => entry.id)
35
+ },
36
+ {
37
+ key: "rejected",
38
+ title: text(t, "flow.rejected.validation", "The server refused the values."),
39
+ ids: refused.map((entry) => entry.id)
40
+ },
41
+ {
42
+ key: "failed",
43
+ title: text(t, "flow.failed", "The request could not be sent."),
44
+ ids: progress.failed
45
+ },
46
+ {
47
+ key: "unknown",
48
+ title: text(t, "flow.unknown", "It may have been applied. Nothing was resent."),
49
+ ids: progress.unknown.filter((id) => !forgotten.has(id))
50
+ },
51
+ {
52
+ key: "unresolved",
53
+ title: text(t, "flow.bulkUnresolved", "Skipped: an earlier request for it may have been applied."),
54
+ ids: progress.unresolved.filter((id) => !forgotten.has(id))
55
+ },
56
+ {
57
+ key: "not-attempted",
58
+ title: text(t, "flow.bulkNotAttempted", "Not attempted."),
59
+ ids: progress.notAttempted
60
+ }
61
+ ];
62
+ return jsxs("div", {
63
+ "data-testid": testid,
64
+ className: "flex flex-col gap-3 text-sm",
65
+ children: [jsx("p", {
66
+ "data-testid": `${testid}:heading`,
67
+ className: "font-medium",
68
+ children: heading
69
+ }), groups.filter((group) => group.ids.length > 0).map((group) => jsxs("section", {
70
+ "data-testid": `${testid}:${group.key}`,
71
+ children: [jsxs("h3", {
72
+ className: "font-medium",
73
+ children: [
74
+ group.title,
75
+ " ",
76
+ jsxs("span", {
77
+ "data-testid": `${testid}:${group.key}:count`,
78
+ className: "tabular-nums",
79
+ children: [
80
+ "(",
81
+ group.ids.length,
82
+ ")"
83
+ ]
84
+ })
85
+ ]
86
+ }), jsxs("ul", {
87
+ className: "flex flex-wrap gap-1 text-muted-foreground",
88
+ children: [group.ids.slice(0, BULK_SUMMARY_IDS).map((id) => jsxs("li", {
89
+ "data-testid": `${testid}:${group.key}:${id}`,
90
+ className: "flex items-center gap-1",
91
+ children: [id, group.key === "unknown" || group.key === "unresolved" ? jsx(Button, {
92
+ type: "button",
93
+ size: "sm",
94
+ variant: "ghost",
95
+ className: "h-6 px-1.5 text-xs",
96
+ "data-testid": `${testid}:forget:${id}`,
97
+ onClick: () => void forget(id).then(() => setForgotten((previous) => new Set([...previous, id]))),
98
+ children: forgetText
99
+ }) : null]
100
+ }, id)), group.ids.length > BULK_SUMMARY_IDS ? jsxs("li", { children: ["+", group.ids.length - BULK_SUMMARY_IDS] }) : null]
101
+ })]
102
+ }, group.key))]
103
+ });
104
+ }
105
+ //#endregion
106
+ export { FlowBulkSummary };
107
+
108
+ //# sourceMappingURL=bulk-summary-GxMDP4bw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-summary-GxMDP4bw.js","names":[],"sources":["../src/flows/bulk-summary.tsx"],"sourcesContent":["'use client';\nimport { BULK_SUMMARY_IDS, type BulkProgress } from '@burojs/core';\nimport { useI18nCaps, useOptionalT, useTestIds } from '@burojs/react';\nimport { useForgetEachAttempt } from '@burojs/react/flows';\nimport { type ReactElement, useState } from 'react';\nimport { Button } from '../primitives/button.js';\n\nexport interface FlowBulkSummaryProps {\n readonly resource: string;\n readonly actionName: string;\n /** The action's label. */\n readonly label: string;\n /** How many ids the run was given. */\n readonly total: number;\n readonly progress: BulkProgress;\n}\n\ntype Translate = ReturnType<typeof useOptionalT>;\nconst text = (t: Translate, key: string, fallback: string, params?: Record<string, unknown>) => {\n const value = t(key, params === undefined ? undefined : { params });\n return value === key ? fallback : value;\n};\n\n/**\n * A bulk flow's summary (P4 spec 4.16): the count that succeeded of the ids given (the flow's\n * own `resources.<r>.actions.<name>.summary`, `{{count}}` of `{{total}}`, else the framework's\n * count text), then one group per other outcome with its count and up to `BULK_SUMMARY_IDS` ids —\n * not processed (gone or its precondition failed), refused, failed, unknown (may have been\n * applied), unresolved from an earlier run, not attempted.\n */\nexport function FlowBulkSummary({\n resource,\n actionName,\n label,\n total,\n progress,\n}: FlowBulkSummaryProps): ReactElement {\n const t = useOptionalT();\n const caps = useI18nCaps(resource);\n // An id the run left unknown, or found unresolved, has its exit here (P4-C review I3): the user\n // checked the record, and the id's attempt is forgotten — the next run sends it again.\n const forget = useForgetEachAttempt({ resource, action: actionName });\n const [forgotten, setForgotten] = useState<ReadonlySet<string>>(() => new Set());\n const forgetText = text(t, 'flow.forget', \"I've checked — forget it\");\n const tid = useTestIds();\n const testid = tid.bulkSummary(resource, actionName);\n const own = `resources.${resource}.actions.${actionName}.summary`;\n const params = { count: progress.success.length, total };\n const heading = caps.has(own)\n ? caps.t(own, { params })\n : `${label}: ${text(t, 'flow.bulkSummary', `${progress.success.length} processed`, params)} / ${total}`;\n const notProcessed = progress.rejected.filter((entry) => entry.reason !== 'server');\n const refused = progress.rejected.filter((entry) => entry.reason === 'server');\n const groups: readonly { key: string; title: string; ids: readonly string[] }[] = [\n {\n key: 'not-processed',\n title: text(t, 'flow.rejected.precondition', 'The record changed. Nothing was sent.'),\n ids: notProcessed.map((entry) => entry.id),\n },\n {\n key: 'rejected',\n title: text(t, 'flow.rejected.validation', 'The server refused the values.'),\n ids: refused.map((entry) => entry.id),\n },\n {\n key: 'failed',\n title: text(t, 'flow.failed', 'The request could not be sent.'),\n ids: progress.failed,\n },\n {\n key: 'unknown',\n title: text(t, 'flow.unknown', 'It may have been applied. Nothing was resent.'),\n ids: progress.unknown.filter((id) => !forgotten.has(id)),\n },\n {\n key: 'unresolved',\n title: text(\n t,\n 'flow.bulkUnresolved',\n 'Skipped: an earlier request for it may have been applied.',\n ),\n ids: progress.unresolved.filter((id) => !forgotten.has(id)),\n },\n {\n key: 'not-attempted',\n title: text(t, 'flow.bulkNotAttempted', 'Not attempted.'),\n ids: progress.notAttempted,\n },\n ];\n return (\n <div data-testid={testid} className=\"flex flex-col gap-3 text-sm\">\n <p data-testid={`${testid}:heading`} className=\"font-medium\">\n {heading}\n </p>\n {groups\n .filter((group) => group.ids.length > 0)\n .map((group) => (\n <section key={group.key} data-testid={`${testid}:${group.key}`}>\n <h3 className=\"font-medium\">\n {group.title}{' '}\n <span data-testid={`${testid}:${group.key}:count`} className=\"tabular-nums\">\n ({group.ids.length})\n </span>\n </h3>\n <ul className=\"flex flex-wrap gap-1 text-muted-foreground\">\n {group.ids.slice(0, BULK_SUMMARY_IDS).map((id) => (\n <li\n key={id}\n data-testid={`${testid}:${group.key}:${id}`}\n className=\"flex items-center gap-1\"\n >\n {id}\n {group.key === 'unknown' || group.key === 'unresolved' ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-6 px-1.5 text-xs\"\n data-testid={`${testid}:forget:${id}`}\n onClick={() =>\n void forget(id).then(() =>\n setForgotten((previous) => new Set([...previous, id])),\n )\n }\n >\n {forgetText}\n </Button>\n ) : null}\n </li>\n ))}\n {group.ids.length > BULK_SUMMARY_IDS ? (\n <li>+{group.ids.length - BULK_SUMMARY_IDS}</li>\n ) : null}\n </ul>\n </section>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ const require_use_theme = require("./use-theme-DFlpXils.cjs");
2
+ let react = require("react");
3
+ react = require_use_theme.__toESM(react, 1);
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ let _radix_ui_react_slot = require("@radix-ui/react-slot");
6
+ //#region src/primitives/button.tsx
7
+ const buttonVariants = (0, require("class-variance-authority").cva)("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 disabled:saturate-0 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:saturate-0 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
11
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
12
+ outline: "border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground",
13
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
14
+ ghost: "hover:bg-accent hover:text-accent-foreground",
15
+ link: "text-primary underline-offset-4 hover:underline"
16
+ },
17
+ size: {
18
+ default: "h-9 px-4 py-2",
19
+ sm: "h-8 rounded-md px-3 text-xs",
20
+ lg: "h-10 rounded-md px-6",
21
+ icon: "h-9 w-9"
22
+ }
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ size: "default"
27
+ }
28
+ });
29
+ const Button = react.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
30
+ return (0, react_jsx_runtime.jsx)(asChild ? _radix_ui_react_slot.Slot : "button", {
31
+ className: require_use_theme.cn(buttonVariants({
32
+ variant,
33
+ size,
34
+ className
35
+ })),
36
+ ref,
37
+ ...props
38
+ });
39
+ });
40
+ Button.displayName = "Button";
41
+ //#endregion
42
+ Object.defineProperty(exports, "Button", {
43
+ enumerable: true,
44
+ get: function() {
45
+ return Button;
46
+ }
47
+ });
48
+ Object.defineProperty(exports, "buttonVariants", {
49
+ enumerable: true,
50
+ get: function() {
51
+ return buttonVariants;
52
+ }
53
+ });
54
+
55
+ //# sourceMappingURL=button-BxnnE28H.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button-BxnnE28H.cjs","names":["cva","React","Slot","cn"],"sources":["../src/primitives/button.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn.js';\n\nconst buttonVariants = cva(\n 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 disabled:saturate-0 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:saturate-0 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground',\n secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n // Shadcn new-york current defaults. The shared stylesheet keeps the\n // standard 16px root; editable controls choose responsive typography\n // locally when they need mobile `text-base` and desktop `md:text-sm`.\n default: 'h-9 px-4 py-2',\n sm: 'h-8 rounded-md px-3 text-xs',\n lg: 'h-10 rounded-md px-6',\n icon: 'h-9 w-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n return (\n <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />\n );\n },\n);\nButton.displayName = 'Button';\n\nexport { Button, buttonVariants };\n"],"mappings":";;;;;;AAOA,MAAM,kBAAA,qCAAiBA,CAAAA,CAAAA,IAAAA,CACrB,ieACA;CACE,UAAU;EACR,SAAS;GACP,SAAS;GACT,aAAa;GACb,SACE;GACF,WAAW;GACX,OAAO;GACP,MAAM;EACR;EACA,MAAM;GAIJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,MAAM;EACR;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;AAQA,MAAM,SAASC,MAAM,YAClB,EAAE,WAAW,SAAS,MAAM,UAAU,OAAO,GAAG,SAAS,QAAQ;CAEhE,QACE,GAAA,kBAAA,IAAA,CAFW,UAAUC,qBAAAA,OAAO,UAE5B;EAAM,WAAWC,kBAAAA,GAAG,eAAe;GAAE;GAAS;GAAM;EAAU,CAAC,CAAC;EAAQ;EAAK,GAAI;CAAQ,CAAA;AAE7F,CACF;AACA,OAAO,cAAc"}
@@ -0,0 +1,44 @@
1
+ import { l as cn } from "./use-theme-dnE3Od3I.js";
2
+ import * as React from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import { Slot } from "@radix-ui/react-slot";
5
+ import { cva } from "class-variance-authority";
6
+ //#region src/primitives/button.tsx
7
+ const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 disabled:saturate-0 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:saturate-0 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
11
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
12
+ outline: "border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground",
13
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
14
+ ghost: "hover:bg-accent hover:text-accent-foreground",
15
+ link: "text-primary underline-offset-4 hover:underline"
16
+ },
17
+ size: {
18
+ default: "h-9 px-4 py-2",
19
+ sm: "h-8 rounded-md px-3 text-xs",
20
+ lg: "h-10 rounded-md px-6",
21
+ icon: "h-9 w-9"
22
+ }
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ size: "default"
27
+ }
28
+ });
29
+ const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
30
+ return jsx(asChild ? Slot : "button", {
31
+ className: cn(buttonVariants({
32
+ variant,
33
+ size,
34
+ className
35
+ })),
36
+ ref,
37
+ ...props
38
+ });
39
+ });
40
+ Button.displayName = "Button";
41
+ //#endregion
42
+ export { buttonVariants as n, Button as t };
43
+
44
+ //# sourceMappingURL=button-MWVeZaqj.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button-MWVeZaqj.js","names":[],"sources":["../src/primitives/button.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn.js';\n\nconst buttonVariants = cva(\n 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 disabled:saturate-0 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:saturate-0 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground',\n secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n // Shadcn new-york current defaults. The shared stylesheet keeps the\n // standard 16px root; editable controls choose responsive typography\n // locally when they need mobile `text-base` and desktop `md:text-sm`.\n default: 'h-9 px-4 py-2',\n sm: 'h-8 rounded-md px-3 text-xs',\n lg: 'h-10 rounded-md px-6',\n icon: 'h-9 w-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n return (\n <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />\n );\n },\n);\nButton.displayName = 'Button';\n\nexport { Button, buttonVariants };\n"],"mappings":";;;;;;AAOA,MAAM,iBAAiB,IACrB,ieACA;CACE,UAAU;EACR,SAAS;GACP,SAAS;GACT,aAAa;GACb,SACE;GACF,WAAW;GACX,OAAO;GACP,MAAM;EACR;EACA,MAAM;GAIJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,MAAM;EACR;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;AAQA,MAAM,SAAS,MAAM,YAClB,EAAE,WAAW,SAAS,MAAM,UAAU,OAAO,GAAG,SAAS,QAAQ;CAEhE,OACE,IAFW,UAAU,OAAO,UAE5B;EAAM,WAAW,GAAG,eAAe;GAAE;GAAS;GAAM;EAAU,CAAC,CAAC;EAAQ;EAAK,GAAI;CAAQ,CAAA;AAE7F,CACF;AACA,OAAO,cAAc"}
@@ -0,0 +1,63 @@
1
+ import { resolveCandidates, resolveFieldLabel } from "@burojs/core";
2
+ import { useApp, useCandidatesAccess, useI18nCaps, useOptionalT, useTestIds } from "@burojs/react";
3
+ import * as React from "react";
4
+ //#region src/fields/candidates-gate.ts
5
+ const NO_SOURCES = Object.freeze({});
6
+ const NO_FILTERS = Object.freeze([]);
7
+ function useCandidatesGate(input) {
8
+ const { to, candidates, sources, resource, fieldName } = input;
9
+ const t = useOptionalT();
10
+ const app = useApp();
11
+ const tid = useTestIds();
12
+ const ownerCaps = useI18nCaps(resource);
13
+ const targetCaps = useI18nCaps(to ?? resource);
14
+ const access = useCandidatesAccess(to ?? "", candidates);
15
+ const resolution = React.useMemo(() => resolveCandidates(candidates, sources ?? NO_SOURCES), [candidates, sources]);
16
+ if (candidates === void 0 || candidates.length === 0) return { filters: NO_FILTERS };
17
+ const testId = tid.relationCandidatesHint(resource, fieldName);
18
+ if (access.status === "pending") return {
19
+ filters: NO_FILTERS,
20
+ gate: { kind: "pending" }
21
+ };
22
+ if (access.status === "forbidden") {
23
+ const declared = (app.registry.get(to ?? "")?.fields)?.[access.field]?.label;
24
+ const label = resolveFieldLabel(targetCaps, access.field, typeof declared === "string" ? declared : void 0);
25
+ return {
26
+ filters: NO_FILTERS,
27
+ gate: {
28
+ kind: "forbidden",
29
+ testId,
30
+ message: t("relationPicker.cannotSearchBy", {
31
+ params: { label },
32
+ defaultValue: `You cannot search by ${label}`
33
+ })
34
+ }
35
+ };
36
+ }
37
+ if (resolution.status === "pending") return {
38
+ filters: NO_FILTERS,
39
+ gate: { kind: "pending" }
40
+ };
41
+ if (resolution.status === "empty") {
42
+ const { source, path } = resolution.hint;
43
+ const head = path[0];
44
+ const declared = (source === "values" || source === "record") && head !== void 0 ? (app.registry.get(resource)?.fields)?.[head]?.label : void 0;
45
+ const label = resolveFieldLabel(ownerCaps, head ?? source, typeof declared === "string" ? declared : void 0);
46
+ return {
47
+ filters: NO_FILTERS,
48
+ gate: {
49
+ kind: "empty",
50
+ testId,
51
+ message: t("relationPicker.chooseFirst", {
52
+ params: { label },
53
+ defaultValue: `Choose ${label} first`
54
+ })
55
+ }
56
+ };
57
+ }
58
+ return { filters: resolution.filters };
59
+ }
60
+ //#endregion
61
+ export { useCandidatesGate as t };
62
+
63
+ //# sourceMappingURL=candidates-gate-CX2PBu6l.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates-gate-CX2PBu6l.js","names":[],"sources":["../src/fields/candidates-gate.ts"],"sourcesContent":["import {\n type FixedFilterInput,\n type RelationCandidate,\n type ScreenSources,\n resolveCandidates,\n resolveFieldLabel,\n} from '@burojs/core';\nimport { useApp, useCandidatesAccess, useI18nCaps, useOptionalT, useTestIds } from '@burojs/react';\nimport * as React from 'react';\n\n/** A select that must not read its target now (P7 3.4): why, and what it tells the user. */\nexport type CandidatesGate =\n | { readonly kind: 'pending' }\n | { readonly kind: 'empty'; readonly message: string; readonly testId: string }\n | { readonly kind: 'forbidden'; readonly message: string; readonly testId: string };\n\nconst NO_SOURCES: ScreenSources = Object.freeze({});\nconst NO_FILTERS: readonly FixedFilterInput[] = Object.freeze([]);\n\n/**\n * A relation field's `candidates` as the select applies them (P7 3.4): the filters to send and,\n * when they say the target must not be read yet, the gate that tells the user why. The refs are\n * read from the owner's projected sources, so a field P5 hides reads `null` (the select asks for\n * it first, and sends nothing); a candidate on a target field the user may not view forbids the\n * read; an undecided source or permission map keeps the select disabled.\n */\nexport function useCandidatesGate(input: {\n readonly to: string | undefined;\n readonly candidates: readonly RelationCandidate[] | undefined;\n readonly sources: ScreenSources | undefined;\n readonly resource: string;\n readonly fieldName: string;\n}): { readonly filters: readonly FixedFilterInput[]; readonly gate?: CandidatesGate } {\n const { to, candidates, sources, resource, fieldName } = input;\n const t = useOptionalT();\n const app = useApp();\n const tid = useTestIds();\n const ownerCaps = useI18nCaps(resource);\n const targetCaps = useI18nCaps(to ?? resource);\n const access = useCandidatesAccess(to ?? '', candidates);\n const resolution = React.useMemo(\n () => resolveCandidates(candidates, sources ?? NO_SOURCES),\n [candidates, sources],\n );\n if (candidates === undefined || candidates.length === 0) return { filters: NO_FILTERS };\n const testId = tid.relationCandidatesHint(resource, fieldName);\n if (access.status === 'pending') return { filters: NO_FILTERS, gate: { kind: 'pending' } };\n if (access.status === 'forbidden') {\n const declared = (\n app.registry.get(to ?? '')?.fields as Record<string, { label?: unknown }> | undefined\n )?.[access.field]?.label;\n const label = resolveFieldLabel(\n targetCaps,\n access.field,\n typeof declared === 'string' ? declared : undefined,\n );\n return {\n filters: NO_FILTERS,\n gate: {\n kind: 'forbidden',\n testId,\n message: t('relationPicker.cannotSearchBy', {\n params: { label },\n defaultValue: `You cannot search by ${label}`,\n }),\n },\n };\n }\n if (resolution.status === 'pending') return { filters: NO_FILTERS, gate: { kind: 'pending' } };\n if (resolution.status === 'empty') {\n const { source, path } = resolution.hint;\n const head = path[0];\n // The field a `values` / `record` ref reads is labelled as the owner labels it; any other\n // source (`scope`, `user`) names the key it reads, humanised.\n const own = source === 'values' || source === 'record';\n const declared =\n own && head !== undefined\n ? (app.registry.get(resource)?.fields as Record<string, { label?: unknown }> | undefined)?.[\n head\n ]?.label\n : undefined;\n const label = resolveFieldLabel(\n ownerCaps,\n head ?? source,\n typeof declared === 'string' ? declared : undefined,\n );\n return {\n filters: NO_FILTERS,\n gate: {\n kind: 'empty',\n testId,\n message: t('relationPicker.chooseFirst', {\n params: { label },\n defaultValue: `Choose ${label} first`,\n }),\n },\n };\n }\n return { filters: resolution.filters };\n}\n"],"mappings":";;;;AAgBA,MAAM,aAA4B,OAAO,OAAO,CAAC,CAAC;AAClD,MAAM,aAA0C,OAAO,OAAO,CAAC,CAAC;AAShE,SAAgB,kBAAkB,OAMoD;CACpF,MAAM,EAAE,IAAI,YAAY,SAAS,UAAU,cAAc;CACzD,MAAM,IAAI,aAAa;CACvB,MAAM,MAAM,OAAO;CACnB,MAAM,MAAM,WAAW;CACvB,MAAM,YAAY,YAAY,QAAQ;CACtC,MAAM,aAAa,YAAY,MAAM,QAAQ;CAC7C,MAAM,SAAS,oBAAoB,MAAM,IAAI,UAAU;CACvD,MAAM,aAAa,MAAM,cACjB,kBAAkB,YAAY,WAAW,UAAU,GACzD,CAAC,YAAY,OAAO,CACtB;CACA,IAAI,eAAe,KAAA,KAAa,WAAW,WAAW,GAAG,OAAO,EAAE,SAAS,WAAW;CACtF,MAAM,SAAS,IAAI,uBAAuB,UAAU,SAAS;CAC7D,IAAI,OAAO,WAAW,WAAW,OAAO;EAAE,SAAS;EAAY,MAAM,EAAE,MAAM,UAAU;CAAE;CACzF,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,YACJ,IAAI,SAAS,IAAI,MAAM,EAAE,CAAC,EAAE,OAAA,GAC1B,OAAO,MAAM,EAAE;EACnB,MAAM,QAAQ,kBACZ,YACA,OAAO,OACP,OAAO,aAAa,WAAW,WAAW,KAAA,CAC5C;EACA,OAAO;GACL,SAAS;GACT,MAAM;IACJ,MAAM;IACN;IACA,SAAS,EAAE,iCAAiC;KAC1C,QAAQ,EAAE,MAAM;KAChB,cAAc,wBAAwB;IACxC,CAAC;GACH;EACF;CACF;CACA,IAAI,WAAW,WAAW,WAAW,OAAO;EAAE,SAAS;EAAY,MAAM,EAAE,MAAM,UAAU;CAAE;CAC7F,IAAI,WAAW,WAAW,SAAS;EACjC,MAAM,EAAE,QAAQ,SAAS,WAAW;EACpC,MAAM,OAAO,KAAK;EAIlB,MAAM,YADM,WAAW,YAAY,WAAW,aAErC,SAAS,KAAA,KACX,IAAI,SAAS,IAAI,QAAQ,CAAC,EAAE,OAAA,GAC3B,KACD,EAAE,QACH,KAAA;EACN,MAAM,QAAQ,kBACZ,WACA,QAAQ,QACR,OAAO,aAAa,WAAW,WAAW,KAAA,CAC5C;EACA,OAAO;GACL,SAAS;GACT,MAAM;IACJ,MAAM;IACN;IACA,SAAS,EAAE,8BAA8B;KACvC,QAAQ,EAAE,MAAM;KAChB,cAAc,UAAU,MAAM;IAChC,CAAC;GACH;EACF;CACF;CACA,OAAO,EAAE,SAAS,WAAW,QAAQ;AACvC"}
@@ -0,0 +1,70 @@
1
+ const require_use_theme = require("./use-theme-DFlpXils.cjs");
2
+ let _burojs_core = require("@burojs/core");
3
+ let _burojs_react = require("@burojs/react");
4
+ let react = require("react");
5
+ react = require_use_theme.__toESM(react, 1);
6
+ //#region src/fields/candidates-gate.ts
7
+ const NO_SOURCES = Object.freeze({});
8
+ const NO_FILTERS = Object.freeze([]);
9
+ function useCandidatesGate(input) {
10
+ const { to, candidates, sources, resource, fieldName } = input;
11
+ const t = (0, _burojs_react.useOptionalT)();
12
+ const app = (0, _burojs_react.useApp)();
13
+ const tid = (0, _burojs_react.useTestIds)();
14
+ const ownerCaps = (0, _burojs_react.useI18nCaps)(resource);
15
+ const targetCaps = (0, _burojs_react.useI18nCaps)(to ?? resource);
16
+ const access = (0, _burojs_react.useCandidatesAccess)(to ?? "", candidates);
17
+ const resolution = react.useMemo(() => (0, _burojs_core.resolveCandidates)(candidates, sources ?? NO_SOURCES), [candidates, sources]);
18
+ if (candidates === void 0 || candidates.length === 0) return { filters: NO_FILTERS };
19
+ const testId = tid.relationCandidatesHint(resource, fieldName);
20
+ if (access.status === "pending") return {
21
+ filters: NO_FILTERS,
22
+ gate: { kind: "pending" }
23
+ };
24
+ if (access.status === "forbidden") {
25
+ const declared = (app.registry.get(to ?? "")?.fields)?.[access.field]?.label;
26
+ const label = (0, _burojs_core.resolveFieldLabel)(targetCaps, access.field, typeof declared === "string" ? declared : void 0);
27
+ return {
28
+ filters: NO_FILTERS,
29
+ gate: {
30
+ kind: "forbidden",
31
+ testId,
32
+ message: t("relationPicker.cannotSearchBy", {
33
+ params: { label },
34
+ defaultValue: `You cannot search by ${label}`
35
+ })
36
+ }
37
+ };
38
+ }
39
+ if (resolution.status === "pending") return {
40
+ filters: NO_FILTERS,
41
+ gate: { kind: "pending" }
42
+ };
43
+ if (resolution.status === "empty") {
44
+ const { source, path } = resolution.hint;
45
+ const head = path[0];
46
+ const declared = (source === "values" || source === "record") && head !== void 0 ? (app.registry.get(resource)?.fields)?.[head]?.label : void 0;
47
+ const label = (0, _burojs_core.resolveFieldLabel)(ownerCaps, head ?? source, typeof declared === "string" ? declared : void 0);
48
+ return {
49
+ filters: NO_FILTERS,
50
+ gate: {
51
+ kind: "empty",
52
+ testId,
53
+ message: t("relationPicker.chooseFirst", {
54
+ params: { label },
55
+ defaultValue: `Choose ${label} first`
56
+ })
57
+ }
58
+ };
59
+ }
60
+ return { filters: resolution.filters };
61
+ }
62
+ //#endregion
63
+ Object.defineProperty(exports, "useCandidatesGate", {
64
+ enumerable: true,
65
+ get: function() {
66
+ return useCandidatesGate;
67
+ }
68
+ });
69
+
70
+ //# sourceMappingURL=candidates-gate-DiFI52r7.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates-gate-DiFI52r7.cjs","names":["useOptionalT","useApp","useTestIds","useI18nCaps","useCandidatesAccess","React","resolveCandidates","resolveFieldLabel"],"sources":["../src/fields/candidates-gate.ts"],"sourcesContent":["import {\n type FixedFilterInput,\n type RelationCandidate,\n type ScreenSources,\n resolveCandidates,\n resolveFieldLabel,\n} from '@burojs/core';\nimport { useApp, useCandidatesAccess, useI18nCaps, useOptionalT, useTestIds } from '@burojs/react';\nimport * as React from 'react';\n\n/** A select that must not read its target now (P7 3.4): why, and what it tells the user. */\nexport type CandidatesGate =\n | { readonly kind: 'pending' }\n | { readonly kind: 'empty'; readonly message: string; readonly testId: string }\n | { readonly kind: 'forbidden'; readonly message: string; readonly testId: string };\n\nconst NO_SOURCES: ScreenSources = Object.freeze({});\nconst NO_FILTERS: readonly FixedFilterInput[] = Object.freeze([]);\n\n/**\n * A relation field's `candidates` as the select applies them (P7 3.4): the filters to send and,\n * when they say the target must not be read yet, the gate that tells the user why. The refs are\n * read from the owner's projected sources, so a field P5 hides reads `null` (the select asks for\n * it first, and sends nothing); a candidate on a target field the user may not view forbids the\n * read; an undecided source or permission map keeps the select disabled.\n */\nexport function useCandidatesGate(input: {\n readonly to: string | undefined;\n readonly candidates: readonly RelationCandidate[] | undefined;\n readonly sources: ScreenSources | undefined;\n readonly resource: string;\n readonly fieldName: string;\n}): { readonly filters: readonly FixedFilterInput[]; readonly gate?: CandidatesGate } {\n const { to, candidates, sources, resource, fieldName } = input;\n const t = useOptionalT();\n const app = useApp();\n const tid = useTestIds();\n const ownerCaps = useI18nCaps(resource);\n const targetCaps = useI18nCaps(to ?? resource);\n const access = useCandidatesAccess(to ?? '', candidates);\n const resolution = React.useMemo(\n () => resolveCandidates(candidates, sources ?? NO_SOURCES),\n [candidates, sources],\n );\n if (candidates === undefined || candidates.length === 0) return { filters: NO_FILTERS };\n const testId = tid.relationCandidatesHint(resource, fieldName);\n if (access.status === 'pending') return { filters: NO_FILTERS, gate: { kind: 'pending' } };\n if (access.status === 'forbidden') {\n const declared = (\n app.registry.get(to ?? '')?.fields as Record<string, { label?: unknown }> | undefined\n )?.[access.field]?.label;\n const label = resolveFieldLabel(\n targetCaps,\n access.field,\n typeof declared === 'string' ? declared : undefined,\n );\n return {\n filters: NO_FILTERS,\n gate: {\n kind: 'forbidden',\n testId,\n message: t('relationPicker.cannotSearchBy', {\n params: { label },\n defaultValue: `You cannot search by ${label}`,\n }),\n },\n };\n }\n if (resolution.status === 'pending') return { filters: NO_FILTERS, gate: { kind: 'pending' } };\n if (resolution.status === 'empty') {\n const { source, path } = resolution.hint;\n const head = path[0];\n // The field a `values` / `record` ref reads is labelled as the owner labels it; any other\n // source (`scope`, `user`) names the key it reads, humanised.\n const own = source === 'values' || source === 'record';\n const declared =\n own && head !== undefined\n ? (app.registry.get(resource)?.fields as Record<string, { label?: unknown }> | undefined)?.[\n head\n ]?.label\n : undefined;\n const label = resolveFieldLabel(\n ownerCaps,\n head ?? source,\n typeof declared === 'string' ? declared : undefined,\n );\n return {\n filters: NO_FILTERS,\n gate: {\n kind: 'empty',\n testId,\n message: t('relationPicker.chooseFirst', {\n params: { label },\n defaultValue: `Choose ${label} first`,\n }),\n },\n };\n }\n return { filters: resolution.filters };\n}\n"],"mappings":";;;;;;AAgBA,MAAM,aAA4B,OAAO,OAAO,CAAC,CAAC;AAClD,MAAM,aAA0C,OAAO,OAAO,CAAC,CAAC;AAShE,SAAgB,kBAAkB,OAMoD;CACpF,MAAM,EAAE,IAAI,YAAY,SAAS,UAAU,cAAc;CACzD,MAAM,KAAA,GAAIA,cAAAA,aAAAA,CAAa;CACvB,MAAM,OAAA,GAAMC,cAAAA,OAAAA,CAAO;CACnB,MAAM,OAAA,GAAMC,cAAAA,WAAAA,CAAW;CACvB,MAAM,aAAA,GAAYC,cAAAA,YAAAA,CAAY,QAAQ;CACtC,MAAM,cAAA,GAAaA,cAAAA,YAAAA,CAAY,MAAM,QAAQ;CAC7C,MAAM,UAAA,GAASC,cAAAA,oBAAAA,CAAoB,MAAM,IAAI,UAAU;CACvD,MAAM,aAAaC,MAAM,eAAA,GACjBC,aAAAA,kBAAAA,CAAkB,YAAY,WAAW,UAAU,GACzD,CAAC,YAAY,OAAO,CACtB;CACA,IAAI,eAAe,KAAA,KAAa,WAAW,WAAW,GAAG,OAAO,EAAE,SAAS,WAAW;CACtF,MAAM,SAAS,IAAI,uBAAuB,UAAU,SAAS;CAC7D,IAAI,OAAO,WAAW,WAAW,OAAO;EAAE,SAAS;EAAY,MAAM,EAAE,MAAM,UAAU;CAAE;CACzF,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,YACJ,IAAI,SAAS,IAAI,MAAM,EAAE,CAAC,EAAE,OAAA,GAC1B,OAAO,MAAM,EAAE;EACnB,MAAM,SAAA,GAAQC,aAAAA,kBAAAA,CACZ,YACA,OAAO,OACP,OAAO,aAAa,WAAW,WAAW,KAAA,CAC5C;EACA,OAAO;GACL,SAAS;GACT,MAAM;IACJ,MAAM;IACN;IACA,SAAS,EAAE,iCAAiC;KAC1C,QAAQ,EAAE,MAAM;KAChB,cAAc,wBAAwB;IACxC,CAAC;GACH;EACF;CACF;CACA,IAAI,WAAW,WAAW,WAAW,OAAO;EAAE,SAAS;EAAY,MAAM,EAAE,MAAM,UAAU;CAAE;CAC7F,IAAI,WAAW,WAAW,SAAS;EACjC,MAAM,EAAE,QAAQ,SAAS,WAAW;EACpC,MAAM,OAAO,KAAK;EAIlB,MAAM,YADM,WAAW,YAAY,WAAW,aAErC,SAAS,KAAA,KACX,IAAI,SAAS,IAAI,QAAQ,CAAC,EAAE,OAAA,GAC3B,KACD,EAAE,QACH,KAAA;EACN,MAAM,SAAA,GAAQA,aAAAA,kBAAAA,CACZ,WACA,QAAQ,QACR,OAAO,aAAa,WAAW,WAAW,KAAA,CAC5C;EACA,OAAO;GACL,SAAS;GACT,MAAM;IACJ,MAAM;IACN;IACA,SAAS,EAAE,8BAA8B;KACvC,QAAQ,EAAE,MAAM;KAChB,cAAc,UAAU,MAAM;IAChC,CAAC;GACH;EACF;CACF;CACA,OAAO,EAAE,SAAS,WAAW,QAAQ;AACvC"}