@opengeni/react 7.4.0-canary.36855269964001 → 7.4.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 (157) hide show
  1. package/README.md +3 -48
  2. package/dist/artifacts-document.js +2 -3
  3. package/dist/artifacts-presentation.js +2 -3
  4. package/dist/artifacts-spreadsheet.js +2 -2
  5. package/dist/artifacts.d.ts +0 -1
  6. package/dist/artifacts.js +1402 -505
  7. package/dist/artifacts.js.map +1 -1
  8. package/dist/{browser-viewer-R6KK5VKR.js → browser-viewer-2E6R2Y4W.js} +4 -4
  9. package/dist/{chunk-WUISZ3WU.js → chunk-3E2AITTZ.js} +28 -32
  10. package/dist/{chunk-WUISZ3WU.js.map → chunk-3E2AITTZ.js.map} +1 -1
  11. package/dist/{chunk-PWUILT2U.js → chunk-5AZSK6MV.js} +2 -2
  12. package/dist/{chunk-Y2PRMYLC.js → chunk-DUEBTL3N.js} +3 -4
  13. package/dist/{chunk-Y2PRMYLC.js.map → chunk-DUEBTL3N.js.map} +1 -1
  14. package/dist/{chunk-7UAMYXJY.js → chunk-KNDIXZN6.js} +18 -25
  15. package/dist/{chunk-7UAMYXJY.js.map → chunk-KNDIXZN6.js.map} +1 -1
  16. package/dist/{chunk-P4G6K2E2.js → chunk-L4OSXQ6H.js} +884 -1119
  17. package/dist/chunk-L4OSXQ6H.js.map +1 -0
  18. package/dist/{chunk-LLIH7Q4B.js → chunk-S5JQ4R22.js} +19 -47
  19. package/dist/chunk-S5JQ4R22.js.map +1 -0
  20. package/dist/{chunk-A4UJB2MM.js → chunk-SEEVCT36.js} +2 -2
  21. package/dist/{chunk-UXJCQ5GH.js → chunk-UDD6DLLT.js} +1 -6
  22. package/dist/chunk-UDD6DLLT.js.map +1 -0
  23. package/dist/chunk-UGMKPUEM.js +299 -0
  24. package/dist/chunk-UGMKPUEM.js.map +1 -0
  25. package/dist/{chunk-ZCZHKJKZ.js → chunk-USXVQACU.js} +3 -3
  26. package/dist/{chunk-VABBF52G.js → chunk-V2ABIVYJ.js} +6 -70
  27. package/dist/chunk-V2ABIVYJ.js.map +1 -0
  28. package/dist/{chunk-TVO6GXR6.js → chunk-X4HIN6TV.js} +162 -71
  29. package/dist/chunk-X4HIN6TV.js.map +1 -0
  30. package/dist/{chunk-IIDEIBMC.js → chunk-XEJEOAOV.js} +38 -49
  31. package/dist/chunk-XEJEOAOV.js.map +1 -0
  32. package/dist/{chunk-ZKLCPBWN.js → chunk-Y3QTZN2F.js} +5 -5
  33. package/dist/{chunk-73BUNGLF.js → chunk-YZ3YYMOM.js} +1 -1
  34. package/dist/components/artifacts/editable-artifact-ui.d.ts +1 -2
  35. package/dist/components/artifacts/index.d.ts +0 -7
  36. package/dist/components/browser-select-control.d.ts +1 -3
  37. package/dist/components/message-timeline.d.ts +1 -18
  38. package/dist/components/open-geni-links.d.ts +0 -16
  39. package/dist/components/session-conversation.d.ts +1 -20
  40. package/dist/composer.js +3 -3
  41. package/dist/{computer-viewer-DJHPSP2J.js → computer-viewer-WMJE6OA3.js} +3 -3
  42. package/dist/connect.js +6 -7
  43. package/dist/connect.js.map +1 -1
  44. package/dist/hooks/latest-question.d.ts +1 -1
  45. package/dist/index.d.ts +2 -5
  46. package/dist/index.js +28 -39
  47. package/dist/index.js.map +1 -1
  48. package/dist/interaction.js +4 -4
  49. package/dist/{latest-question-KGCDK3XX.js → latest-question-QB4EUCW5.js} +3 -3
  50. package/dist/lib/format.d.ts +0 -3
  51. package/dist/machines.js +2 -2
  52. package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-PFUEZ2U4.js} +5 -37
  53. package/dist/markdown-table-layout-PFUEZ2U4.js.map +1 -0
  54. package/dist/{sandbox-files-4PMXDSDV.js → sandbox-files-PXPIZAFG.js} +2 -2
  55. package/dist/session-ui.d.ts +2 -5
  56. package/dist/session-ui.js +13 -23
  57. package/dist/session-ui.js.map +1 -1
  58. package/dist/session.js +3 -3
  59. package/dist/sites.js +289 -4
  60. package/dist/sites.js.map +1 -1
  61. package/dist/timeline/types.d.ts +1 -7
  62. package/dist/{timeline-annotation-selection-DMLMM2TX.js → timeline-annotation-selection-ODLVF4OI.js} +1 -6
  63. package/dist/timeline-annotation-selection-ODLVF4OI.js.map +1 -0
  64. package/package.json +3 -7
  65. package/src/artifacts.ts +0 -37
  66. package/src/components/artifacts/editable-artifact-ui.tsx +1 -3
  67. package/src/components/artifacts/editable-artifact-workbench.tsx +2 -5
  68. package/src/components/artifacts/editable-document.tsx +36 -34
  69. package/src/components/artifacts/editable-presentation.tsx +16 -21
  70. package/src/components/artifacts/index.ts +0 -41
  71. package/src/components/browser-select-control.tsx +1 -4
  72. package/src/components/browser-viewer.tsx +22 -81
  73. package/src/components/computer-viewer.tsx +23 -51
  74. package/src/components/markdown-table-layout.ts +5 -49
  75. package/src/components/markdown.tsx +2 -5
  76. package/src/components/message-timeline.tsx +434 -515
  77. package/src/components/open-geni-links.tsx +0 -24
  78. package/src/components/session-conversation.tsx +20 -131
  79. package/src/components/timeline-annotation-selection.tsx +0 -7
  80. package/src/hooks/use-available-models.ts +1 -1
  81. package/src/index.ts +0 -9
  82. package/src/lib/format.ts +0 -13
  83. package/src/session-ui.ts +0 -9
  84. package/src/timeline/projection.ts +4 -78
  85. package/src/timeline/types.ts +0 -7
  86. package/styles/compiled.css +0 -325
  87. package/dist/chat-interactive-block-VAGABN7J.js +0 -11
  88. package/dist/chunk-5CVXZA3Y.js +0 -292
  89. package/dist/chunk-5CVXZA3Y.js.map +0 -1
  90. package/dist/chunk-B3V6TG77.js +0 -41
  91. package/dist/chunk-B3V6TG77.js.map +0 -1
  92. package/dist/chunk-IIDEIBMC.js.map +0 -1
  93. package/dist/chunk-LLIH7Q4B.js.map +0 -1
  94. package/dist/chunk-P4G6K2E2.js.map +0 -1
  95. package/dist/chunk-RAIQYITC.js +0 -1772
  96. package/dist/chunk-RAIQYITC.js.map +0 -1
  97. package/dist/chunk-SCOTPE2I.js +0 -589
  98. package/dist/chunk-SCOTPE2I.js.map +0 -1
  99. package/dist/chunk-TVO6GXR6.js.map +0 -1
  100. package/dist/chunk-UXJCQ5GH.js.map +0 -1
  101. package/dist/chunk-VABBF52G.js.map +0 -1
  102. package/dist/chunk-WEHMPOXK.js +0 -39
  103. package/dist/chunk-WEHMPOXK.js.map +0 -1
  104. package/dist/chunk-XCHWDJ22.js +0 -107
  105. package/dist/chunk-XCHWDJ22.js.map +0 -1
  106. package/dist/components/artifacts/artifact-chrome.d.ts +0 -120
  107. package/dist/components/artifacts/artifact-sandbox.d.ts +0 -27
  108. package/dist/components/artifacts/chat-interactive-block.d.ts +0 -29
  109. package/dist/components/artifacts/deferred-chat-media.d.ts +0 -15
  110. package/dist/components/artifacts/editable-artifact-view.d.ts +0 -46
  111. package/dist/components/artifacts/latest-task-runner.d.ts +0 -15
  112. package/dist/components/artifacts/session-artifact-viewer.d.ts +0 -39
  113. package/dist/components/artifacts/site-view.d.ts +0 -23
  114. package/dist/components/timeline-pill-geometry.d.ts +0 -6
  115. package/dist/components/timeline-pill-placement.d.ts +0 -18
  116. package/dist/components/usage-limit-notice.d.ts +0 -49
  117. package/dist/components/usage-member-list.d.ts +0 -91
  118. package/dist/components/usage-meter.d.ts +0 -73
  119. package/dist/hooks/use-usage.d.ts +0 -40
  120. package/dist/markdown-table-layout-QXSLK4BY.js.map +0 -1
  121. package/dist/sandbox-files-4PMXDSDV.js.map +0 -1
  122. package/dist/timeline/allowance-exhausted-row.d.ts +0 -24
  123. package/dist/timeline-annotation-selection-DMLMM2TX.js.map +0 -1
  124. package/dist/timeline-pill-geometry-2G6EK47V.js +0 -73
  125. package/dist/timeline-pill-geometry-2G6EK47V.js.map +0 -1
  126. package/dist/usage/allowance-copy.d.ts +0 -25
  127. package/dist/usage/summary.d.ts +0 -35
  128. package/dist/usage.d.ts +0 -13
  129. package/dist/usage.js +0 -1073
  130. package/dist/usage.js.map +0 -1
  131. package/src/components/artifacts/artifact-chrome.tsx +0 -413
  132. package/src/components/artifacts/artifact-sandbox.tsx +0 -185
  133. package/src/components/artifacts/chat-interactive-block.tsx +0 -260
  134. package/src/components/artifacts/deferred-chat-media.tsx +0 -67
  135. package/src/components/artifacts/editable-artifact-view.tsx +0 -187
  136. package/src/components/artifacts/latest-task-runner.ts +0 -49
  137. package/src/components/artifacts/session-artifact-viewer.tsx +0 -376
  138. package/src/components/artifacts/site-view.tsx +0 -116
  139. package/src/components/timeline-pill-geometry.ts +0 -112
  140. package/src/components/timeline-pill-placement.ts +0 -81
  141. package/src/components/usage-limit-notice.tsx +0 -179
  142. package/src/components/usage-member-list.tsx +0 -792
  143. package/src/components/usage-meter.tsx +0 -382
  144. package/src/hooks/use-usage.ts +0 -100
  145. package/src/timeline/allowance-exhausted-row.tsx +0 -86
  146. package/src/usage/allowance-copy.ts +0 -64
  147. package/src/usage/summary.ts +0 -93
  148. package/src/usage.ts +0 -50
  149. /package/dist/{browser-viewer-R6KK5VKR.js.map → browser-viewer-2E6R2Y4W.js.map} +0 -0
  150. /package/dist/{chunk-PWUILT2U.js.map → chunk-5AZSK6MV.js.map} +0 -0
  151. /package/dist/{chunk-A4UJB2MM.js.map → chunk-SEEVCT36.js.map} +0 -0
  152. /package/dist/{chunk-ZCZHKJKZ.js.map → chunk-USXVQACU.js.map} +0 -0
  153. /package/dist/{chunk-ZKLCPBWN.js.map → chunk-Y3QTZN2F.js.map} +0 -0
  154. /package/dist/{chunk-73BUNGLF.js.map → chunk-YZ3YYMOM.js.map} +0 -0
  155. /package/dist/{chat-interactive-block-VAGABN7J.js.map → computer-viewer-WMJE6OA3.js.map} +0 -0
  156. /package/dist/{latest-question-KGCDK3XX.js.map → latest-question-QB4EUCW5.js.map} +0 -0
  157. /package/dist/{computer-viewer-DJHPSP2J.js.map → sandbox-files-PXPIZAFG.js.map} +0 -0
package/dist/usage.js DELETED
@@ -1,1073 +0,0 @@
1
- import {
2
- DEFAULT_ALLOWANCE_LABELS,
3
- allowanceLabels,
4
- allowanceResetSentence,
5
- formatAllowanceDate,
6
- formatAllowanceInstant
7
- } from "./chunk-B3V6TG77.js";
8
- import {
9
- usePolledValue
10
- } from "./chunk-KZ73RL76.js";
11
- import {
12
- OpenGeniContext
13
- } from "./chunk-NEXLLTP4.js";
14
- import {
15
- cn
16
- } from "./chunk-22KP6LR5.js";
17
-
18
- // src/hooks/use-usage.ts
19
- import {
20
- getMyUsage
21
- } from "@opengeni/sdk/usage-allowances";
22
- import { useCallback, useContext, useEffect, useMemo, useRef } from "react";
23
-
24
- // src/usage/summary.ts
25
- function reading(scope, usage) {
26
- if (usage.limit === null) return null;
27
- return {
28
- scope,
29
- fraction: usage.fraction ?? (usage.limit === 0 ? 1 : usage.used / usage.limit),
30
- status: usage.status,
31
- resetsAt: usage.resetsAt,
32
- used: usage.used,
33
- limit: usage.limit,
34
- remaining: Math.max(0, usage.remaining ?? usage.limit - usage.used)
35
- };
36
- }
37
- function summarizeUsage(usage, subjectId) {
38
- const row = subjectId === void 0 ? usage.members[0] : usage.members.find((member2) => member2.subjectId === subjectId);
39
- const member = row ? reading("member", row) : null;
40
- const workspace = reading("workspace", usage.workspace);
41
- const readings = [member, workspace].filter((value) => value !== null);
42
- if (readings.length === 0) return { state: "unlimited", binding: null, member, workspace };
43
- const exhausted = (workspace?.status === "exhausted" ? workspace : null) ?? (member?.status === "exhausted" ? member : null);
44
- if (exhausted) return { state: "exhausted", binding: exhausted, member, workspace };
45
- const warnings = readings.filter((value) => value.status === "warning");
46
- const candidates = warnings.length > 0 ? warnings : readings;
47
- const binding = candidates.reduce(
48
- (tightest, value) => value.remaining < tightest.remaining ? value : tightest
49
- );
50
- return { state: warnings.length > 0 ? "warning" : "ok", binding, member, workspace };
51
- }
52
- function usagePercentLabel(fraction) {
53
- if (!Number.isFinite(fraction) || fraction <= 0) return "0";
54
- if (fraction < 0.01) return "<1";
55
- if (fraction < 1 && Math.round(fraction * 100) === 100) return "99";
56
- return String(Math.round(fraction * 100));
57
- }
58
-
59
- // src/hooks/use-usage.ts
60
- function readOwnUsage(client, workspaceId, period, signal) {
61
- const query = period === void 0 ? {} : { period };
62
- const options = signal ? { signal } : {};
63
- if ("getMyUsage" in client && typeof client.getMyUsage === "function") {
64
- return client.getMyUsage(workspaceId, query, options);
65
- }
66
- if ("requestJson" in client && typeof client.requestJson === "function") {
67
- return getMyUsage(client, workspaceId, query, options);
68
- }
69
- throw new Error("@opengeni/react: the usage client needs getMyUsage or requestJson.");
70
- }
71
- function useUsage(options = {}) {
72
- const context = useContext(OpenGeniContext);
73
- const client = options.client ?? context?.client;
74
- const workspaceId = options.workspaceId ?? context?.workspaceId;
75
- if (!client || !workspaceId) {
76
- throw new Error(
77
- "@opengeni/react: useUsage needs a client and workspace. Wrap the tree in <OpenGeniProvider> or pass { client, workspaceId }."
78
- );
79
- }
80
- const period = options.period;
81
- const load = useCallback(
82
- (signal) => readOwnUsage(client, workspaceId, period, signal),
83
- [client, workspaceId, period]
84
- );
85
- const state = usePolledValue(load, {
86
- pollIntervalMs: options.pollIntervalMs,
87
- enabled: options.enabled
88
- });
89
- const refreshKey = options.refreshKey;
90
- const seenKey = useRef(refreshKey);
91
- const refresh = state.refresh;
92
- useEffect(() => {
93
- if (Object.is(seenKey.current, refreshKey)) return;
94
- seenKey.current = refreshKey;
95
- void refresh();
96
- }, [refreshKey, refresh]);
97
- const usage = state.data;
98
- const summary = useMemo(() => usage ? summarizeUsage(usage) : null, [usage]);
99
- return { usage, summary, loading: state.loading, error: state.error, refresh };
100
- }
101
-
102
- // src/components/usage-meter.tsx
103
- import { jsx, jsxs } from "react/jsx-runtime";
104
- var DEFAULT_USAGE_METER_LABELS = {
105
- ...allowanceLabels(),
106
- title: "Your usage",
107
- workspaceTitle: "Workspace usage",
108
- left: (percent) => `${percent}% left`,
109
- used: (percent) => `${percent}% used`,
110
- limitReached: "Limit reached",
111
- nearLimit: "Near limit",
112
- amountLeft: (amount) => `${amount} left`,
113
- unlimited: "No usage limit",
114
- loading: "Checking usage\u2026",
115
- error: "Couldn't load usage",
116
- retry: "Try again",
117
- amounts: (used, limit) => `${used} of ${limit} used`,
118
- workspaceLine: (value) => `Workspace: ${value}`
119
- };
120
- function UsageMeter({
121
- usage,
122
- client,
123
- workspaceId,
124
- period,
125
- refreshKey,
126
- ...view
127
- }) {
128
- if (usage !== void 0) {
129
- return /* @__PURE__ */ jsx(
130
- UsageMeterView,
131
- {
132
- ...view,
133
- summary: usage ? summarizeUsage(usage) : null,
134
- loading: usage === null
135
- }
136
- );
137
- }
138
- return /* @__PURE__ */ jsx(
139
- LoadingUsageMeter,
140
- {
141
- ...view,
142
- client,
143
- workspaceId,
144
- period,
145
- refreshKey
146
- }
147
- );
148
- }
149
- function LoadingUsageMeter({
150
- client,
151
- workspaceId,
152
- period,
153
- refreshKey,
154
- ...view
155
- }) {
156
- const state = useUsage({ client, workspaceId, period, refreshKey });
157
- return /* @__PURE__ */ jsx(
158
- UsageMeterView,
159
- {
160
- ...view,
161
- summary: state.summary,
162
- loading: state.loading && !state.summary,
163
- error: state.summary ? null : state.error,
164
- onRetry: () => void state.refresh()
165
- }
166
- );
167
- }
168
- var FILL = {
169
- ok: "bg-og-accent",
170
- warning: "bg-og-status-waiting",
171
- exhausted: "bg-og-danger"
172
- };
173
- function valueText(reading2, measure, labels) {
174
- if (reading2.status === "exhausted") return labels.limitReached;
175
- return measure === "left" ? labels.left(usagePercentLabel(Math.max(0, 1 - reading2.fraction))) : labels.used(usagePercentLabel(reading2.fraction));
176
- }
177
- function UsageMeterView({
178
- summary,
179
- loading = false,
180
- error = null,
181
- onRetry,
182
- measure = "left",
183
- formatAmount,
184
- labels: overrides,
185
- density = "default",
186
- showWorkspace = true,
187
- className
188
- }) {
189
- const labels = { ...DEFAULT_USAGE_METER_LABELS, ...overrides };
190
- const compact = density === "compact";
191
- if (loading || error || !summary) {
192
- return /* @__PURE__ */ jsx(
193
- "div",
194
- {
195
- className: cn("og-root min-w-0 text-og-sm", className),
196
- "data-og-usage-meter": error ? "error" : "loading",
197
- "aria-busy": loading || void 0,
198
- children: error ? /* @__PURE__ */ jsxs("p", { className: "flex flex-wrap items-center gap-x-2 text-og-fg-muted", role: "alert", children: [
199
- labels.error,
200
- onRetry ? /* @__PURE__ */ jsx(
201
- "button",
202
- {
203
- type: "button",
204
- onClick: onRetry,
205
- className: "rounded-og-sm text-og-fg underline-offset-2 hover:underline focus-visible:outline-2 focus-visible:outline-og-accent",
206
- children: labels.retry
207
- }
208
- ) : null
209
- ] }) : /* @__PURE__ */ jsx("span", { className: "text-og-fg-subtle", children: labels.loading })
210
- }
211
- );
212
- }
213
- const primary = summary.member ?? summary.workspace;
214
- if (!primary) {
215
- return /* @__PURE__ */ jsxs(
216
- "div",
217
- {
218
- className: cn("og-root min-w-0 text-og-sm text-og-fg-muted", className),
219
- "data-og-usage-meter": "unlimited",
220
- children: [
221
- compact ? null : /* @__PURE__ */ jsxs("span", { className: "font-medium text-og-fg", children: [
222
- labels.title,
223
- " \xB7 "
224
- ] }),
225
- labels.unlimited
226
- ]
227
- }
228
- );
229
- }
230
- const title = summary.member ? labels.title : labels.workspaceTitle;
231
- const value = valueText(primary, measure, labels);
232
- const fill = Math.min(1, Math.max(0, primary.fraction));
233
- const reset = allowanceResetSentence(labels, primary.resetsAt);
234
- const pool = showWorkspace && summary.member && summary.workspace && (summary.binding?.scope === "workspace" || summary.workspace.status !== "ok") ? summary.workspace : null;
235
- const meterProps = {
236
- role: "meter",
237
- "aria-label": title || labels.title,
238
- "aria-valuemin": 0,
239
- "aria-valuemax": 100,
240
- "aria-valuenow": Math.round(fill * 100),
241
- "aria-valuetext": `${value}. ${reset}${pool ? ` ${labels.workspaceLine(valueText(pool, measure, labels))}.` : ""}`
242
- };
243
- const bar = /* @__PURE__ */ jsx(
244
- "span",
245
- {
246
- "aria-hidden": true,
247
- className: cn(
248
- "relative block overflow-hidden rounded-full",
249
- density === "hero" ? "h-1.5" : "h-1",
250
- primary.status === "exhausted" ? "bg-og-danger/15" : "bg-og-surface-3",
251
- compact ? "w-16 shrink-0" : "w-full"
252
- ),
253
- children: /* @__PURE__ */ jsx(
254
- "span",
255
- {
256
- className: cn("absolute inset-y-0 left-0 rounded-full", FILL[primary.status]),
257
- style: { width: `${Math.round(fill * 1e3) / 10}%` }
258
- }
259
- )
260
- }
261
- );
262
- const valueClass = cn(
263
- "shrink-0 whitespace-nowrap tabular-nums",
264
- primary.status === "ok" ? "text-og-fg-muted" : "font-medium",
265
- primary.status === "warning" && "text-og-status-waiting",
266
- primary.status === "exhausted" && "text-og-danger"
267
- );
268
- if (compact) {
269
- return /* @__PURE__ */ jsxs(
270
- "span",
271
- {
272
- ...meterProps,
273
- "data-og-usage-meter": primary.status,
274
- title: primary.resetsAt ? `${reset} ${formatAllowanceInstant(primary.resetsAt)}` : reset,
275
- className: cn("og-root inline-flex min-w-0 items-center gap-2 text-og-xs", className),
276
- children: [
277
- /* @__PURE__ */ jsx("span", { className: valueClass, children: value }),
278
- bar
279
- ]
280
- }
281
- );
282
- }
283
- const amounts = formatAmount ? labels.amounts(formatAmount(primary.used), formatAmount(primary.limit)) : null;
284
- if (density === "hero") {
285
- const headline = formatAmount ? labels.amountLeft(formatAmount(primary.remaining)) : labels.left(usagePercentLabel(Math.max(0, 1 - primary.fraction)));
286
- return /* @__PURE__ */ jsxs(
287
- "div",
288
- {
289
- ...meterProps,
290
- "data-og-usage-meter": primary.status,
291
- className: cn("og-root flex min-w-0 flex-col gap-3", className),
292
- children: [
293
- title ? /* @__PURE__ */ jsx("span", { className: "text-og-sm font-medium text-og-fg-muted", children: title }) : null,
294
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1", children: [
295
- /* @__PURE__ */ jsx("span", { className: "text-2xl leading-8 font-semibold tracking-[-0.5px] text-og-fg tabular-nums", children: headline }),
296
- primary.status !== "ok" ? /* @__PURE__ */ jsxs(
297
- "span",
298
- {
299
- className: cn(
300
- "inline-flex h-[22px] items-center gap-1.5 rounded-full border px-2 text-og-xs font-medium",
301
- primary.status === "warning" ? "border-og-status-waiting/30 text-og-status-waiting" : "border-og-danger/30 text-og-danger"
302
- ),
303
- children: [
304
- /* @__PURE__ */ jsx(
305
- "span",
306
- {
307
- "aria-hidden": true,
308
- className: cn(
309
- "size-1.5 rounded-full",
310
- primary.status === "warning" ? "bg-og-status-waiting" : "bg-og-danger"
311
- )
312
- }
313
- ),
314
- primary.status === "warning" ? labels.nearLimit : labels.limitReached
315
- ]
316
- }
317
- ) : null
318
- ] }),
319
- bar,
320
- /* @__PURE__ */ jsxs("p", { className: "text-og-xs text-og-fg-muted tabular-nums", children: [
321
- [
322
- amounts ?? labels.used(usagePercentLabel(primary.fraction)),
323
- amounts ? `${usagePercentLabel(primary.fraction)}%` : null
324
- ].filter(Boolean).join(" \xB7 "),
325
- " ",
326
- "\xB7",
327
- " ",
328
- /* @__PURE__ */ jsx("span", { title: primary.resetsAt ? formatAllowanceInstant(primary.resetsAt) : void 0, children: reset })
329
- ] }),
330
- pool ? /* @__PURE__ */ jsx(PoolLine, { reading: pool, measure, labels }) : null
331
- ]
332
- }
333
- );
334
- }
335
- return /* @__PURE__ */ jsxs(
336
- "div",
337
- {
338
- ...meterProps,
339
- "data-og-usage-meter": primary.status,
340
- className: cn("og-root flex min-w-0 flex-col gap-2 text-og-sm", className),
341
- children: [
342
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-baseline justify-between gap-3", children: [
343
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate font-medium text-og-fg", children: title }),
344
- /* @__PURE__ */ jsx("span", { className: valueClass, children: value })
345
- ] }),
346
- bar,
347
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-baseline justify-between gap-x-3 gap-y-1 text-og-xs text-og-fg-subtle", children: [
348
- /* @__PURE__ */ jsx("span", { title: primary.resetsAt ? formatAllowanceInstant(primary.resetsAt) : void 0, children: reset }),
349
- amounts ? /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: amounts }) : null
350
- ] }),
351
- pool ? /* @__PURE__ */ jsx(PoolLine, { reading: pool, measure, labels }) : null
352
- ]
353
- }
354
- );
355
- }
356
- function PoolLine({
357
- reading: reading2,
358
- measure,
359
- labels
360
- }) {
361
- return /* @__PURE__ */ jsx(
362
- "p",
363
- {
364
- className: cn(
365
- "text-og-xs",
366
- reading2.status === "exhausted" ? "font-medium text-og-danger" : reading2.status === "warning" ? "text-og-status-waiting" : "text-og-fg-muted"
367
- ),
368
- children: labels.workspaceLine(valueText(reading2, measure, labels))
369
- }
370
- );
371
- }
372
-
373
- // src/components/usage-limit-notice.tsx
374
- import { GaugeIcon, XIcon } from "lucide-react";
375
- import { useEffect as useEffect2, useState } from "react";
376
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
377
- var DEFAULT_USAGE_LIMIT_NOTICE_LABELS = {
378
- ...allowanceLabels(),
379
- memberWarning: (percent) => `You've used ${percent}% of your usage limit.`,
380
- workspaceWarning: (percent) => `This workspace has used ${percent}% of its usage budget.`,
381
- memberExhausted: "You've reached your usage limit.",
382
- workspaceExhausted: "This workspace has reached its usage limit.",
383
- dismiss: "Dismiss"
384
- };
385
- function UsageLimitNotice({
386
- usage,
387
- client,
388
- workspaceId,
389
- period,
390
- refreshKey,
391
- ...view
392
- }) {
393
- if (usage !== void 0) {
394
- return /* @__PURE__ */ jsx2(UsageLimitNoticeView, { ...view, summary: usage ? summarizeUsage(usage) : null });
395
- }
396
- return /* @__PURE__ */ jsx2(
397
- LoadingNotice,
398
- {
399
- ...view,
400
- client,
401
- workspaceId,
402
- period,
403
- refreshKey
404
- }
405
- );
406
- }
407
- function LoadingNotice({
408
- client,
409
- workspaceId,
410
- period,
411
- refreshKey,
412
- ...view
413
- }) {
414
- const { summary } = useUsage({ client, workspaceId, period, refreshKey });
415
- return /* @__PURE__ */ jsx2(UsageLimitNoticeView, { ...view, summary });
416
- }
417
- function readDismissed(key) {
418
- if (!key || typeof sessionStorage === "undefined") return false;
419
- try {
420
- return sessionStorage.getItem(key) === "1";
421
- } catch {
422
- return false;
423
- }
424
- }
425
- function UsageLimitNoticeView({
426
- summary,
427
- labels: overrides,
428
- action,
429
- dismissStorageKey,
430
- className
431
- }) {
432
- const labels = { ...DEFAULT_USAGE_LIMIT_NOTICE_LABELS, ...overrides };
433
- const binding = summary?.binding ?? null;
434
- const state = summary?.state;
435
- const key = dismissStorageKey && binding && state === "warning" ? `${dismissStorageKey}:warning:${binding.scope}:${binding.resetsAt ?? "none"}` : null;
436
- const [dismissed, setDismissed] = useState(() => readDismissed(key));
437
- useEffect2(() => setDismissed(readDismissed(key)), [key]);
438
- if (!summary || !binding || state !== "warning" && state !== "exhausted") return null;
439
- if (state === "warning" && dismissed) return null;
440
- const workspace = binding.scope === "workspace";
441
- const exhausted = state === "exhausted";
442
- const headline = exhausted ? workspace ? labels.workspaceExhausted : labels.memberExhausted : workspace ? labels.workspaceWarning(usagePercentLabel(binding.fraction)) : labels.memberWarning(usagePercentLabel(binding.fraction));
443
- const reset = allowanceResetSentence(labels, binding.resetsAt);
444
- const dismiss = () => {
445
- setDismissed(true);
446
- if (!key) return;
447
- try {
448
- sessionStorage.setItem(key, "1");
449
- } catch {
450
- }
451
- };
452
- return /* @__PURE__ */ jsxs2(
453
- "div",
454
- {
455
- role: "status",
456
- "data-og-usage-notice": exhausted ? "exhausted" : "warning",
457
- "data-og-usage-scope": binding.scope,
458
- className: cn(
459
- "og-root flex min-w-0 items-start gap-2 px-3.5 pt-2.5 text-og-sm md:px-4",
460
- className
461
- ),
462
- children: [
463
- /* @__PURE__ */ jsx2(GaugeIcon, { "aria-hidden": true, className: "mt-0.5 size-3.5 shrink-0 text-og-status-waiting" }),
464
- /* @__PURE__ */ jsxs2("p", { className: "min-w-0 flex-1 text-og-fg-muted", children: [
465
- /* @__PURE__ */ jsx2("span", { className: cn(exhausted && "font-medium text-og-fg"), children: headline }),
466
- " ",
467
- exhausted ? /* @__PURE__ */ jsxs2(Fragment, { children: [
468
- workspace ? labels.workspaceRemedy : labels.memberRemedy,
469
- " "
470
- ] }) : null,
471
- /* @__PURE__ */ jsx2(
472
- "span",
473
- {
474
- className: "whitespace-nowrap",
475
- title: binding.resetsAt ? formatAllowanceInstant(binding.resetsAt) : void 0,
476
- children: reset
477
- }
478
- )
479
- ] }),
480
- action ? /* @__PURE__ */ jsx2("div", { className: "shrink-0", children: action(summary) }) : null,
481
- !exhausted ? /* @__PURE__ */ jsx2(
482
- "button",
483
- {
484
- type: "button",
485
- "aria-label": labels.dismiss,
486
- onClick: dismiss,
487
- className: "-mt-0.5 -mr-1 grid size-6 shrink-0 place-items-center rounded-og-sm text-og-fg-subtle transition hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-2 focus-visible:outline-og-accent pointer-coarse:size-9",
488
- children: /* @__PURE__ */ jsx2(XIcon, { "aria-hidden": true, className: "size-3.5" })
489
- }
490
- ) : null
491
- ]
492
- }
493
- );
494
- }
495
-
496
- // src/components/usage-member-list.tsx
497
- import { Slider } from "radix-ui";
498
- import { useId, useMemo as useMemo2, useState as useState2 } from "react";
499
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
500
- var DEFAULT_USAGE_MEMBER_LIST_LABELS = {
501
- listLabel: "Member usage",
502
- used: (percent) => `${percent}% used`,
503
- noLimitUsed: "No individual limit",
504
- amounts: (used, limit) => `${used} of ${limit}`,
505
- nearLimit: "Near limit",
506
- limitReached: "Limit reached",
507
- limit: "Limit",
508
- defaultEqualShare: (percent) => `Equal share \xB7 ${percent}%`,
509
- defaultNone: "No individual limit",
510
- defaultShare: (percent) => `Default \xB7 ${percent}%`,
511
- defaultAmount: (amount) => `Default \xB7 ${amount}`,
512
- share: (percent) => `${percent}% of budget`,
513
- amount: (amount) => `${amount} fixed`,
514
- fixedAmountHidden: "Fixed amount",
515
- edit: (name) => `Change limit for ${name}`,
516
- editorTitle: (name) => `Limit for ${name}`,
517
- optionDefault: "Default",
518
- optionShare: "Share of budget",
519
- optionAmount: "Fixed amount",
520
- fixedAmountHint: "Each period. Doesn't grow when the budget does.",
521
- shareSlider: "Share of the workspace budget",
522
- shareInput: "Share, percent",
523
- amountInput: "Fixed amount",
524
- equalShareMultiple: (multiple) => `${multiple}\xD7 an equal share`,
525
- sameAsEqualShare: "About an equal share",
526
- atBudget: (amount) => `About ${amount} at the current budget`,
527
- save: "Save",
528
- saving: "Saving\u2026",
529
- cancel: "Cancel",
530
- saveFailed: "Couldn't save the limit. Refresh and try again.",
531
- invalidAmount: "Enter an amount of zero or more.",
532
- totalWithin: (percent) => `Member limits add up to ${percent}% of the budget.`,
533
- totalOver: (percent) => `Member limits add up to ${percent}% of the budget.`,
534
- oversubscribedHelp: "That's allowed: limits are ceilings, not reservations. The workspace budget still caps what everyone spends together.",
535
- unlimitedMembers: (count) => count === 1 ? "1 member has no individual limit." : `${count} members have no individual limit.`,
536
- empty: "No members yet."
537
- };
538
- var STATUS_RANK = { exhausted: 0, warning: 1, ok: 2 };
539
- function shareBase(usage) {
540
- return usage.workspace.includedCredits + usage.workspace.grantsRemaining;
541
- }
542
- function formatPercent(value) {
543
- const scaled = value * 100;
544
- return Number.isInteger(Math.round(scaled * 10) / 10) || scaled >= 10 ? String(Math.round(scaled)) : (Math.round(scaled * 10) / 10).toFixed(1);
545
- }
546
- function formatMultiple(value) {
547
- return value >= 10 ? String(Math.round(value)) : (Math.round(value * 10) / 10).toString();
548
- }
549
- function describeMemberLimit(member, context) {
550
- const labels = { ...DEFAULT_USAGE_MEMBER_LIST_LABELS, ...context.labels };
551
- const ofBudget = (limit) => limit !== null && context.shareBase > 0 ? formatPercent(limit / context.shareBase) : "0";
552
- const rule = member.rule;
553
- if (rule && "share" in rule) return labels.share(formatPercent(rule.share));
554
- if (rule && "credits" in rule) {
555
- return context.amounts ? labels.amount(context.amounts.format(rule.credits)) : labels.fixedAmountHidden;
556
- }
557
- const fallback = context.memberDefault ?? "none";
558
- if (fallback === "none" || member.limit === null) return labels.defaultNone;
559
- if (fallback === "equal_share") return labels.defaultEqualShare(ofBudget(member.limit));
560
- if ("share" in fallback) return labels.defaultShare(formatPercent(fallback.share));
561
- return context.amounts ? labels.defaultAmount(context.amounts.format(fallback.credits)) : labels.defaultShare(ofBudget(member.limit));
562
- }
563
- function UsageMemberList({
564
- usage,
565
- memberDefault,
566
- describe,
567
- onChangeRule,
568
- amounts,
569
- labels: overrides,
570
- framed = true,
571
- className
572
- }) {
573
- const labels = { ...DEFAULT_USAGE_MEMBER_LIST_LABELS, ...overrides };
574
- const base = shareBase(usage);
575
- const [editing, setEditing] = useState2(null);
576
- const name = (member) => describe?.(member) ?? {
577
- name: member.externalIdentity?.externalId ?? member.subjectId,
578
- detail: null
579
- };
580
- const members = useMemo2(
581
- () => [...usage.members].sort(
582
- (left, right) => STATUS_RANK[left.status] - STATUS_RANK[right.status] || right.used - left.used
583
- ),
584
- [usage.members]
585
- );
586
- if (members.length === 0) {
587
- return /* @__PURE__ */ jsx3("p", { className: cn("og-root text-og-sm text-og-fg-muted", className), children: labels.empty });
588
- }
589
- return /* @__PURE__ */ jsxs3("div", { className: cn("og-root flex min-w-0 flex-col gap-3", className), children: [
590
- /* @__PURE__ */ jsx3(AllocationSummary, { usage, base, labels, framed }),
591
- /* @__PURE__ */ jsx3(
592
- "ul",
593
- {
594
- "aria-label": labels.listLabel,
595
- className: cn(
596
- "divide-y divide-og-border",
597
- framed && "overflow-hidden rounded-og-md border border-og-border"
598
- ),
599
- children: members.map((member) => /* @__PURE__ */ jsx3(
600
- MemberRow,
601
- {
602
- member,
603
- identity: name(member),
604
- usage,
605
- base,
606
- memberDefault,
607
- amounts,
608
- labels,
609
- framed,
610
- editing: editing === member.subjectId,
611
- onEdit: onChangeRule ? () => setEditing(
612
- (current) => current === member.subjectId ? null : member.subjectId
613
- ) : void 0,
614
- onClose: () => setEditing(null),
615
- onSave: onChangeRule ? async (rule) => {
616
- await onChangeRule(member, rule);
617
- setEditing(null);
618
- } : void 0
619
- },
620
- member.subjectId
621
- ))
622
- }
623
- )
624
- ] });
625
- }
626
- function memberShares(usage, base) {
627
- let total = 0;
628
- let unlimited = 0;
629
- for (const member of usage.members) {
630
- if (member.limit === null) unlimited += 1;
631
- else total += base > 0 ? member.limit / base : 0;
632
- }
633
- return { total, unlimited };
634
- }
635
- function AllocationSummary({
636
- usage,
637
- base,
638
- labels,
639
- framed
640
- }) {
641
- if (base <= 0) return null;
642
- const { total, unlimited } = memberShares(usage, base);
643
- const limited = usage.members.length - unlimited;
644
- const over = total > 1.005;
645
- const shaped = usage.members.some((member) => member.rule !== null);
646
- if (limited === 0 || !over && !shaped && unlimited === 0) return null;
647
- return /* @__PURE__ */ jsxs3(
648
- "div",
649
- {
650
- "data-og-usage-allocation": over ? "oversubscribed" : "within",
651
- className: cn(
652
- "flex min-w-0 flex-col gap-1 text-og-sm",
653
- framed || over ? "rounded-og-md px-3 py-2" : "pt-1",
654
- over ? "bg-og-status-waiting/8 text-og-fg" : framed ? "bg-og-surface-1 text-og-fg-muted" : "text-og-fg-muted"
655
- ),
656
- children: [
657
- limited > 0 ? /* @__PURE__ */ jsx3(AllocationBar, { total, over, children: over ? labels.totalOver(formatPercent(total)) : labels.totalWithin(formatPercent(total)) }) : null,
658
- over ? /* @__PURE__ */ jsx3("p", { className: "text-og-xs text-og-fg-muted", children: labels.oversubscribedHelp }) : null,
659
- unlimited > 0 ? /* @__PURE__ */ jsx3("p", { className: "text-og-xs text-og-fg-muted", children: labels.unlimitedMembers(unlimited) }) : null
660
- ]
661
- }
662
- );
663
- }
664
- function AllocationBar({
665
- total,
666
- over,
667
- children
668
- }) {
669
- const scale = Math.max(1, total);
670
- return /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-col gap-1.5", children: [
671
- /* @__PURE__ */ jsx3("p", { className: cn(over && "font-medium"), children }),
672
- /* @__PURE__ */ jsxs3("span", { "aria-hidden": true, className: "relative block h-1 overflow-hidden rounded-full bg-og-surface-3", children: [
673
- /* @__PURE__ */ jsx3(
674
- "span",
675
- {
676
- className: "absolute inset-y-0 left-0 bg-og-accent/70",
677
- style: { width: `${Math.min(total, 1) / scale * 100}%` }
678
- }
679
- ),
680
- over ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
681
- /* @__PURE__ */ jsx3(
682
- "span",
683
- {
684
- className: "absolute inset-y-0 bg-og-status-waiting",
685
- style: { left: `${1 / scale * 100}%`, right: 0 }
686
- }
687
- ),
688
- /* @__PURE__ */ jsx3(
689
- "span",
690
- {
691
- className: "absolute inset-y-[-2px] w-px bg-og-fg",
692
- style: { left: `${1 / scale * 100}%` }
693
- }
694
- )
695
- ] }) : null
696
- ] })
697
- ] });
698
- }
699
- function MemberRow({
700
- member,
701
- identity,
702
- usage,
703
- base,
704
- memberDefault,
705
- amounts,
706
- labels,
707
- framed,
708
- editing,
709
- onEdit,
710
- onClose,
711
- onSave
712
- }) {
713
- const editorId = useId();
714
- const limited = member.limit !== null;
715
- const fraction = member.fraction ?? 0;
716
- const limitText = describeMemberLimit(member, {
717
- memberDefault,
718
- shareBase: base,
719
- amounts,
720
- labels
721
- });
722
- return /* @__PURE__ */ jsxs3("li", { className: cn("min-w-0", framed && "bg-og-bg"), "data-og-usage-member": member.subjectId, children: [
723
- /* @__PURE__ */ jsxs3(
724
- "div",
725
- {
726
- className: cn(
727
- "grid min-w-0 grid-cols-1 gap-x-4 gap-y-2 py-3 sm:grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)_11rem] sm:items-center",
728
- framed && "px-3.5"
729
- ),
730
- children: [
731
- /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
732
- /* @__PURE__ */ jsx3("p", { className: "truncate text-og-sm font-medium text-og-fg", children: identity.name }),
733
- identity.detail ? /* @__PURE__ */ jsx3("p", { className: "truncate text-og-xs text-og-fg-subtle", children: identity.detail }) : null
734
- ] }),
735
- /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-col gap-1", children: [
736
- /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-baseline justify-between gap-2 text-og-xs", children: [
737
- /* @__PURE__ */ jsx3(
738
- "span",
739
- {
740
- className: cn(
741
- "tabular-nums",
742
- member.status === "ok" && "text-og-fg-muted",
743
- member.status === "warning" && "font-medium text-og-status-waiting",
744
- member.status === "exhausted" && "font-medium text-og-danger"
745
- ),
746
- children: limited ? member.status === "exhausted" ? labels.limitReached : `${labels.used(usagePercentLabel(fraction))}${member.status === "warning" ? ` \xB7 ${labels.nearLimit}` : ""}` : labels.noLimitUsed
747
- }
748
- ),
749
- amounts ? /* @__PURE__ */ jsx3("span", { className: "truncate text-og-fg-subtle tabular-nums", children: limited ? labels.amounts(amounts.format(member.used), amounts.format(member.limit)) : amounts.format(member.used) }) : null
750
- ] }),
751
- /* @__PURE__ */ jsx3(
752
- "span",
753
- {
754
- "aria-hidden": true,
755
- className: cn(
756
- "relative block h-1 overflow-hidden rounded-full",
757
- limited ? "bg-og-surface-3" : "bg-transparent"
758
- ),
759
- children: limited ? /* @__PURE__ */ jsx3(
760
- "span",
761
- {
762
- className: cn(
763
- "absolute inset-y-0 left-0 rounded-full",
764
- member.status === "ok" ? "bg-og-accent" : member.status === "warning" ? "bg-og-status-waiting" : "bg-og-danger"
765
- ),
766
- style: { width: `${Math.min(1, Math.max(0, fraction)) * 100}%` }
767
- }
768
- ) : null
769
- }
770
- )
771
- ] }),
772
- /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center justify-between gap-2 sm:justify-end", children: [
773
- /* @__PURE__ */ jsx3("span", { className: "text-og-xs text-og-fg-subtle sm:hidden", children: labels.limit }),
774
- onEdit ? /* @__PURE__ */ jsx3(
775
- "button",
776
- {
777
- type: "button",
778
- onClick: onEdit,
779
- "aria-expanded": editing,
780
- "aria-controls": editing ? editorId : void 0,
781
- "aria-label": `${labels.edit(identity.name)}: ${limitText}`,
782
- className: "inline-flex h-8 max-w-full items-center gap-1.5 truncate rounded-og-sm border border-og-border px-2.5 text-og-control text-og-fg transition hover:bg-og-surface-2 focus-visible:outline-2 focus-visible:outline-og-accent pointer-coarse:h-10",
783
- children: limitText
784
- }
785
- ) : /* @__PURE__ */ jsx3("span", { className: "truncate text-og-xs text-og-fg-muted", children: limitText })
786
- ] })
787
- ]
788
- }
789
- ),
790
- editing && onSave ? /* @__PURE__ */ jsx3(
791
- MemberLimitEditor,
792
- {
793
- id: editorId,
794
- member,
795
- name: identity.name,
796
- usage,
797
- base,
798
- memberDefault,
799
- amounts,
800
- labels,
801
- framed,
802
- onCancel: onClose,
803
- onSave
804
- }
805
- ) : null
806
- ] });
807
- }
808
- function initialMode(rule, amounts) {
809
- if (rule === null) return "share";
810
- if ("share" in rule) return "share";
811
- return amounts ? "amount" : "share";
812
- }
813
- function MemberLimitEditor({
814
- id,
815
- member,
816
- name,
817
- usage,
818
- base,
819
- memberDefault,
820
- amounts,
821
- labels,
822
- framed,
823
- onCancel,
824
- onSave
825
- }) {
826
- const count = Math.max(1, usage.members.length);
827
- const equal = 1 / count;
828
- const currentShare = member.rule && "share" in member.rule ? member.rule.share : member.limit !== null && base > 0 ? member.limit / base : equal;
829
- const startPercent = Math.min(100, Math.max(0, Math.round(currentShare * 100)));
830
- const startAmount = amounts && member.rule && "credits" in member.rule ? stripPrefix(amounts.format(member.rule.credits), amounts.prefix) : amounts && member.limit !== null ? stripPrefix(amounts.format(member.limit), amounts.prefix) : "";
831
- const [mode, setMode] = useState2(() => initialMode(member.rule, amounts));
832
- const [sharePercent, setSharePercent] = useState2(startPercent);
833
- const [amountText, setAmountText] = useState2(startAmount);
834
- const [saving, setSaving] = useState2(false);
835
- const [error, setError] = useState2(null);
836
- const share = sharePercent / 100;
837
- const parsedAmount = amounts && mode === "amount" ? amounts.parse(amountText) : null;
838
- const defaultLimitText = describeMemberLimit(
839
- {
840
- rule: null,
841
- limit: member.rule === null ? member.limit : defaultLimitFor(memberDefault, base, count)
842
- },
843
- { memberDefault, shareBase: base, amounts, labels }
844
- );
845
- const unchanged = mode === "default" ? member.rule === null : mode === "share" ? (member.rule === null || "share" in member.rule) && sharePercent === startPercent : member.rule !== null && "credits" in member.rule && amountText === startAmount;
846
- const draftLimit = mode === "share" ? share * base : mode === "amount" ? parsedAmount : defaultLimitFor(memberDefault, base, count);
847
- const others = usage.members.reduce(
848
- (sum, other) => other.subjectId === member.subjectId || other.limit === null ? sum : sum + other.limit,
849
- 0
850
- );
851
- const draftTotal = base > 0 && draftLimit !== null ? (others + draftLimit) / base : null;
852
- async function save() {
853
- let rule;
854
- if (mode === "default") rule = null;
855
- else if (mode === "share") rule = { share };
856
- else {
857
- if (parsedAmount === null || !Number.isSafeInteger(parsedAmount) || parsedAmount < 0) {
858
- setError(labels.invalidAmount);
859
- return;
860
- }
861
- rule = { credits: parsedAmount };
862
- }
863
- setSaving(true);
864
- setError(null);
865
- try {
866
- await onSave(rule);
867
- } catch (cause) {
868
- setError(cause instanceof Error && cause.message ? cause.message : labels.saveFailed);
869
- setSaving(false);
870
- }
871
- }
872
- const modes = [
873
- { value: "share", label: labels.optionShare },
874
- ...amounts ? [{ value: "amount", label: labels.optionAmount }] : [],
875
- { value: "default", label: labels.optionDefault }
876
- ];
877
- return /* @__PURE__ */ jsxs3(
878
- "div",
879
- {
880
- id,
881
- role: "group",
882
- "aria-label": labels.editorTitle(name),
883
- className: cn(
884
- "bg-og-surface-1 px-3.5 py-3",
885
- framed ? "border-t border-og-border" : "mb-3 rounded-og-md"
886
- ),
887
- onKeyDown: (event) => {
888
- if (event.key === "Escape" && !saving) {
889
- event.stopPropagation();
890
- onCancel();
891
- }
892
- },
893
- children: [
894
- /* @__PURE__ */ jsxs3("fieldset", { className: "flex min-w-0 flex-col gap-3", disabled: saving, children: [
895
- /* @__PURE__ */ jsx3("legend", { className: "sr-only", children: labels.editorTitle(name) }),
896
- /* @__PURE__ */ jsx3(
897
- "div",
898
- {
899
- role: "radiogroup",
900
- "aria-label": labels.limit,
901
- className: "inline-flex w-fit max-w-full flex-wrap gap-0.5 rounded-og-sm bg-og-surface-3/60 p-0.5",
902
- children: modes.map((option) => /* @__PURE__ */ jsxs3(
903
- "label",
904
- {
905
- className: cn(
906
- "relative inline-flex h-7 cursor-pointer items-center rounded-[5px] px-2.5 text-og-control transition has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-og-accent pointer-coarse:h-9",
907
- mode === option.value ? "bg-og-bg font-medium text-og-fg shadow-sm" : "text-og-fg-muted hover:text-og-fg"
908
- ),
909
- children: [
910
- /* @__PURE__ */ jsx3(
911
- "input",
912
- {
913
- type: "radio",
914
- name: `${id}-mode`,
915
- value: option.value,
916
- checked: mode === option.value,
917
- onChange: () => setMode(option.value),
918
- className: "sr-only"
919
- }
920
- ),
921
- option.label
922
- ]
923
- },
924
- option.value
925
- ))
926
- }
927
- ),
928
- mode === "share" ? /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-col gap-2", children: [
929
- /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-3", children: [
930
- /* @__PURE__ */ jsxs3(
931
- Slider.Root,
932
- {
933
- value: [sharePercent],
934
- min: 0,
935
- max: 100,
936
- step: 1,
937
- onValueChange: (next) => setSharePercent(next[0] ?? 0),
938
- "data-og-share-slider": "",
939
- className: "relative flex h-6 min-w-0 flex-1 touch-none items-center select-none pointer-coarse:h-10",
940
- children: [
941
- /* @__PURE__ */ jsx3(Slider.Track, { className: "relative h-1.5 grow overflow-hidden rounded-full bg-og-surface-3", children: /* @__PURE__ */ jsx3(Slider.Range, { className: "absolute h-full rounded-full bg-og-accent" }) }),
942
- count > 1 ? /* @__PURE__ */ jsx3(
943
- "span",
944
- {
945
- "aria-hidden": true,
946
- className: "pointer-events-none absolute top-1/2 h-3.5 w-0.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-og-fg-subtle",
947
- style: { left: `${equal * 100}%` }
948
- }
949
- ) : null,
950
- /* @__PURE__ */ jsx3(
951
- Slider.Thumb,
952
- {
953
- "aria-label": labels.shareSlider,
954
- "aria-valuetext": `${sharePercent}%`,
955
- className: "block size-4 rounded-full border-2 border-og-accent bg-og-bg shadow-sm transition focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-og-accent pointer-coarse:size-6"
956
- }
957
- )
958
- ]
959
- }
960
- ),
961
- /* @__PURE__ */ jsxs3("label", { className: "flex shrink-0 items-center gap-1 text-og-sm text-og-fg", children: [
962
- /* @__PURE__ */ jsx3(
963
- "input",
964
- {
965
- type: "number",
966
- inputMode: "numeric",
967
- min: 0,
968
- max: 100,
969
- step: 1,
970
- value: sharePercent,
971
- "aria-label": labels.shareInput,
972
- onChange: (event) => {
973
- const next = Number(event.target.value);
974
- if (Number.isFinite(next))
975
- setSharePercent(Math.min(100, Math.max(0, Math.round(next))));
976
- },
977
- className: "h-8 w-16 rounded-og-sm border border-og-border bg-og-bg px-2 text-right tabular-nums focus-visible:outline-2 focus-visible:outline-og-accent pointer-coarse:h-10"
978
- }
979
- ),
980
- "%"
981
- ] })
982
- ] }),
983
- /* @__PURE__ */ jsx3("p", { className: "text-og-xs text-og-fg-subtle", "aria-live": "polite", children: [
984
- count > 1 ? Math.abs(share * count - 1) < 0.05 ? labels.sameAsEqualShare : labels.equalShareMultiple(formatMultiple(share * count)) : null,
985
- amounts && base > 0 ? labels.atBudget(amounts.format(Math.round(share * base))) : null
986
- ].filter(Boolean).join(" \xB7 ") })
987
- ] }) : mode === "amount" && amounts ? /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 text-og-sm text-og-fg", children: [
988
- amounts.prefix ? /* @__PURE__ */ jsx3("span", { className: "text-og-fg-subtle", children: amounts.prefix }) : null,
989
- /* @__PURE__ */ jsx3(
990
- "input",
991
- {
992
- type: "number",
993
- inputMode: "decimal",
994
- min: 0,
995
- step: amounts.step ?? 0.01,
996
- value: amountText,
997
- "aria-label": labels.amountInput,
998
- "aria-invalid": error === labels.invalidAmount || void 0,
999
- onChange: (event) => setAmountText(event.target.value),
1000
- className: "h-8 w-28 rounded-og-sm border border-og-border bg-og-bg px-2 tabular-nums focus-visible:outline-2 focus-visible:outline-og-accent pointer-coarse:h-10"
1001
- }
1002
- ),
1003
- /* @__PURE__ */ jsx3("span", { className: "ml-2 text-og-xs text-og-fg-subtle", children: labels.fixedAmountHint })
1004
- ] }) : /* @__PURE__ */ jsx3("p", { className: "text-og-sm text-og-fg-muted", children: defaultLimitText })
1005
- ] }),
1006
- draftTotal !== null ? /* @__PURE__ */ jsx3(
1007
- "p",
1008
- {
1009
- className: cn("mt-3 text-og-xs", draftTotal > 1.005 ? "text-og-fg" : "text-og-fg-subtle"),
1010
- children: draftTotal > 1.005 ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1011
- /* @__PURE__ */ jsx3("span", { className: "font-medium", children: labels.totalOver(formatPercent(draftTotal)) }),
1012
- " ",
1013
- /* @__PURE__ */ jsx3("span", { className: "text-og-fg-muted", children: labels.oversubscribedHelp })
1014
- ] }) : labels.totalWithin(formatPercent(draftTotal))
1015
- }
1016
- ) : null,
1017
- error ? /* @__PURE__ */ jsx3("p", { role: "alert", className: "mt-2 text-og-xs text-og-danger", children: error }) : null,
1018
- /* @__PURE__ */ jsxs3("div", { className: "mt-3 flex items-center justify-end gap-2", children: [
1019
- /* @__PURE__ */ jsx3(
1020
- "button",
1021
- {
1022
- type: "button",
1023
- onClick: onCancel,
1024
- disabled: saving,
1025
- className: "inline-flex h-8 items-center rounded-og-sm px-3 text-og-control text-og-fg transition hover:bg-og-surface-2 focus-visible:outline-2 focus-visible:outline-og-accent disabled:opacity-50 pointer-coarse:h-10",
1026
- children: labels.cancel
1027
- }
1028
- ),
1029
- /* @__PURE__ */ jsx3(
1030
- "button",
1031
- {
1032
- type: "button",
1033
- onClick: () => void save(),
1034
- disabled: saving || unchanged,
1035
- className: "inline-flex h-8 items-center rounded-og-sm border border-og-primary-border bg-og-primary px-3 text-og-control font-medium text-og-primary-fg transition hover:bg-og-primary-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:h-10",
1036
- children: saving ? labels.saving : labels.save
1037
- }
1038
- )
1039
- ] })
1040
- ]
1041
- }
1042
- );
1043
- }
1044
- function stripPrefix(text, prefix) {
1045
- const plain = prefix && text.startsWith(prefix) ? text.slice(prefix.length) : text;
1046
- return plain.replace(/,/g, "");
1047
- }
1048
- function defaultLimitFor(memberDefault, base, count) {
1049
- const fallback = memberDefault ?? "none";
1050
- if (fallback === "none") return null;
1051
- if (fallback === "equal_share") return base / count;
1052
- if ("share" in fallback) return fallback.share * base;
1053
- return fallback.credits;
1054
- }
1055
- export {
1056
- DEFAULT_ALLOWANCE_LABELS,
1057
- DEFAULT_USAGE_LIMIT_NOTICE_LABELS,
1058
- DEFAULT_USAGE_MEMBER_LIST_LABELS,
1059
- DEFAULT_USAGE_METER_LABELS,
1060
- UsageLimitNotice,
1061
- UsageLimitNoticeView,
1062
- UsageMemberList,
1063
- UsageMeter,
1064
- UsageMeterView,
1065
- allowanceLabels,
1066
- allowanceResetSentence,
1067
- describeMemberLimit,
1068
- formatAllowanceDate,
1069
- summarizeUsage,
1070
- usagePercentLabel,
1071
- useUsage
1072
- };
1073
- //# sourceMappingURL=usage.js.map