@usethrottle/auth 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/dist/AccountDashboard-V63ghvjY.d.ts +90 -0
- package/dist/AccountModal-KA54WUFW.js +13 -0
- package/dist/AccountModal-KA54WUFW.js.map +1 -0
- package/dist/AccountModal-pIMgledL.d.ts +141 -0
- package/dist/AuthModal-T2GYI6ZF.js +13 -0
- package/dist/AuthModal-T2GYI6ZF.js.map +1 -0
- package/dist/SignUpPage-BJcMUb2V.d.ts +160 -0
- package/dist/chunk-3B4DNX5T.js +2 -0
- package/dist/chunk-3B4DNX5T.js.map +1 -0
- package/dist/chunk-5EUZYJJL.js +29 -0
- package/dist/chunk-5EUZYJJL.js.map +1 -0
- package/dist/chunk-6WN3J5MM.js +2075 -0
- package/dist/chunk-6WN3J5MM.js.map +1 -0
- package/dist/chunk-CUNMJ2PH.js +1320 -0
- package/dist/chunk-CUNMJ2PH.js.map +1 -0
- package/dist/chunk-ETYOOYYV.js +2 -0
- package/dist/chunk-ETYOOYYV.js.map +1 -0
- package/dist/chunk-JSDZMPCV.js +520 -0
- package/dist/chunk-JSDZMPCV.js.map +1 -0
- package/dist/chunk-QPVS6JXP.js +39 -0
- package/dist/chunk-QPVS6JXP.js.map +1 -0
- package/dist/chunk-RCNUWITB.js +747 -0
- package/dist/chunk-RCNUWITB.js.map +1 -0
- package/dist/{chunk-YY6YATXT.js → chunk-TDQC3LMQ.js} +17 -3
- package/dist/chunk-TDQC3LMQ.js.map +1 -0
- package/dist/{chunk-EFMKBOTL.js → chunk-UFGW76AZ.js} +2 -2
- package/dist/chunk-VDK23T6L.js +230 -0
- package/dist/chunk-VDK23T6L.js.map +1 -0
- package/dist/chunk-X6D35EAX.js +19 -0
- package/dist/chunk-X6D35EAX.js.map +1 -0
- package/dist/client-7LVKfuXO.d.ts +319 -0
- package/dist/hooks-DkpLrEiC.d.ts +148 -0
- package/dist/index.cjs +16 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/localization-SnIAZXJn.d.ts +389 -0
- package/dist/react/components/account/index.d.ts +37 -0
- package/dist/react/components/account/index.js +44 -0
- package/dist/react/components/account/index.js.map +1 -0
- package/dist/react/components/auth/index.d.ts +17 -0
- package/dist/react/components/auth/index.js +40 -0
- package/dist/react/components/auth/index.js.map +1 -0
- package/dist/react/components/guards.d.ts +21 -0
- package/dist/react/components/guards.js +13 -0
- package/dist/react/components/guards.js.map +1 -0
- package/dist/react/components/index.cjs +3561 -2805
- package/dist/react/components/index.cjs.map +1 -1
- package/dist/react/components/index.d.cts +14 -2
- package/dist/react/components/index.d.ts +14 -821
- package/dist/react/components/index.js +90 -4680
- package/dist/react/components/index.js.map +1 -1
- package/dist/react/components/provider.d.ts +79 -0
- package/dist/react/components/provider.js +18 -0
- package/dist/react/components/provider.js.map +1 -0
- package/dist/react/forms/index.js +1 -1
- package/dist/react/index.cjs +16 -2
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +34 -3
- package/dist/react/index.d.ts +5 -433
- package/dist/react/index.js +3 -2
- package/dist/styles.css +7 -4
- package/package.json +4 -1
- package/dist/chunk-YY6YATXT.js.map +0 -1
- /package/dist/{chunk-EFMKBOTL.js.map → chunk-UFGW76AZ.js.map} +0 -0
|
@@ -0,0 +1,1320 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
messageForError
|
|
4
|
+
} from "./chunk-K7RCWADI.js";
|
|
5
|
+
|
|
6
|
+
// src/react/components/appearance.tsx
|
|
7
|
+
import { createContext, useContext, useMemo } from "react";
|
|
8
|
+
|
|
9
|
+
// src/react/components/contrast.ts
|
|
10
|
+
var DEFAULT_DANGER = { light: "#c2321f", dark: "#ff6b58" };
|
|
11
|
+
var DEFAULT_PRIMARY_FOREGROUND = { light: "#ffffff", dark: "#0d1117" };
|
|
12
|
+
function channel(value) {
|
|
13
|
+
const c = value / 255;
|
|
14
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
15
|
+
}
|
|
16
|
+
function luminance(hex) {
|
|
17
|
+
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
18
|
+
if (!m) return null;
|
|
19
|
+
const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
|
|
20
|
+
const [r, g, b] = [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
21
|
+
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
|
22
|
+
}
|
|
23
|
+
function contrastRatio(a, b) {
|
|
24
|
+
const la = luminance(a);
|
|
25
|
+
const lb = luminance(b);
|
|
26
|
+
if (la === null || lb === null) return null;
|
|
27
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
28
|
+
}
|
|
29
|
+
function primaryContrastWarning(variables, theme) {
|
|
30
|
+
const primary = variables.colorPrimary;
|
|
31
|
+
if (!primary) return null;
|
|
32
|
+
const themes = theme === "auto" ? ["light", "dark"] : [theme];
|
|
33
|
+
const failures = [];
|
|
34
|
+
for (const t of themes) {
|
|
35
|
+
const fg = variables.colorPrimaryForeground ?? DEFAULT_PRIMARY_FOREGROUND[t];
|
|
36
|
+
const ratio = contrastRatio(primary, fg);
|
|
37
|
+
if (ratio === null) return null;
|
|
38
|
+
if (ratio < 4.5) failures.push(`${t} ${ratio.toFixed(2)}:1 against ${fg}`);
|
|
39
|
+
}
|
|
40
|
+
if (failures.length === 0) return null;
|
|
41
|
+
const advice = variables.colorPrimaryForeground ? "Pick a foreground with more contrast against it." : "You set `colorPrimary` without `colorPrimaryForeground`, so the theme default is being used. Set both.";
|
|
42
|
+
return `[@usethrottle/auth] Primary button text may be hard to read on ${primary}: ${failures.join("; ")}. WCAG asks for 4.5:1. ${advice}`;
|
|
43
|
+
}
|
|
44
|
+
function toLab(hex) {
|
|
45
|
+
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
46
|
+
if (!m) return null;
|
|
47
|
+
const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
|
|
48
|
+
const [r, g, b] = [0, 2, 4].map((i) => {
|
|
49
|
+
const c = parseInt(h.slice(i, i + 2), 16) / 255;
|
|
50
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
51
|
+
});
|
|
52
|
+
const X = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
|
|
53
|
+
const Y = r * 0.2126 + g * 0.7152 + b * 0.0722;
|
|
54
|
+
const Z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
|
|
55
|
+
const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
|
|
56
|
+
const [fx, fy, fz] = [f(X), f(Y), f(Z)];
|
|
57
|
+
return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];
|
|
58
|
+
}
|
|
59
|
+
function colourDistance(a, b) {
|
|
60
|
+
const la = toLab(a);
|
|
61
|
+
const lb = toLab(b);
|
|
62
|
+
if (!la || !lb) return null;
|
|
63
|
+
return Math.hypot(la[0] - lb[0], la[1] - lb[1], la[2] - lb[2]);
|
|
64
|
+
}
|
|
65
|
+
var MIN_DISTINCT = 25;
|
|
66
|
+
function dangerCollisionWarning(variables, theme) {
|
|
67
|
+
const primary = variables.colorPrimary;
|
|
68
|
+
if (!primary) return null;
|
|
69
|
+
const themes = theme === "auto" ? ["light", "dark"] : [theme];
|
|
70
|
+
const failures = [];
|
|
71
|
+
for (const t of themes) {
|
|
72
|
+
const danger = variables.colorDanger ?? DEFAULT_DANGER[t];
|
|
73
|
+
const d = colourDistance(primary, danger);
|
|
74
|
+
if (d === null) return null;
|
|
75
|
+
if (d < MIN_DISTINCT) failures.push(`${t} \u0394E ${d.toFixed(1)} against ${danger}`);
|
|
76
|
+
}
|
|
77
|
+
if (failures.length === 0) return null;
|
|
78
|
+
return `[@usethrottle/auth] Your brand colour ${primary} is too close to the destructive colour to tell apart: ${failures.join("; ")}. Delete and Edit sit side by side in the account panels and a buyer distinguishes them by colour alone, so pick a more distinct \`colorPrimary\`, or override \`colorDanger\` to something further from it.`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/react/components/appearance.tsx
|
|
82
|
+
import { jsx } from "react/jsx-runtime";
|
|
83
|
+
var warnedPairings = /* @__PURE__ */ new Set();
|
|
84
|
+
function warnOnUnreadablePrimary(variables, theme) {
|
|
85
|
+
if (process.env.NODE_ENV === "production") return;
|
|
86
|
+
const key = `${variables.colorPrimary}|${variables.colorPrimaryForeground}|${variables.colorDanger}|${theme}`;
|
|
87
|
+
if (warnedPairings.has(key)) return;
|
|
88
|
+
const messages = [
|
|
89
|
+
primaryContrastWarning(variables, theme),
|
|
90
|
+
dangerCollisionWarning(variables, theme)
|
|
91
|
+
].filter(Boolean);
|
|
92
|
+
if (messages.length === 0) return;
|
|
93
|
+
warnedPairings.add(key);
|
|
94
|
+
for (const m of messages) console.warn(m);
|
|
95
|
+
}
|
|
96
|
+
var VARIABLE_PROPERTY = {
|
|
97
|
+
colorPrimary: "--throttle-color-primary",
|
|
98
|
+
colorPrimaryForeground: "--throttle-color-primary-foreground",
|
|
99
|
+
colorBackground: "--throttle-color-background",
|
|
100
|
+
colorSurface: "--throttle-color-surface",
|
|
101
|
+
colorForeground: "--throttle-color-foreground",
|
|
102
|
+
colorMuted: "--throttle-color-muted",
|
|
103
|
+
colorMutedForeground: "--throttle-color-muted-foreground",
|
|
104
|
+
colorBorder: "--throttle-color-border",
|
|
105
|
+
colorBorderControl: "--throttle-color-border-control",
|
|
106
|
+
colorDanger: "--throttle-color-danger",
|
|
107
|
+
colorDangerSurface: "--throttle-color-danger-surface",
|
|
108
|
+
colorDangerBorder: "--throttle-color-danger-border",
|
|
109
|
+
colorSuccess: "--throttle-color-success",
|
|
110
|
+
colorSuccessSurface: "--throttle-color-success-surface",
|
|
111
|
+
colorSuccessBorder: "--throttle-color-success-border",
|
|
112
|
+
colorWarning: "--throttle-color-warning",
|
|
113
|
+
colorWarningSurface: "--throttle-color-warning-surface",
|
|
114
|
+
colorWarningBorder: "--throttle-color-warning-border",
|
|
115
|
+
fontFamily: "--throttle-font-family",
|
|
116
|
+
fontSize: "--throttle-font-size",
|
|
117
|
+
borderRadius: "--throttle-radius",
|
|
118
|
+
borderWidth: "--throttle-border-width",
|
|
119
|
+
spacing: "--throttle-spacing",
|
|
120
|
+
// The account dashboard's own surface and outer padding. Separate from
|
|
121
|
+
// colorBackground/colorSurface because a merchant embedding the dashboard
|
|
122
|
+
// in their page usually wants it to paint nothing at all, while still
|
|
123
|
+
// theming the cards inside it.
|
|
124
|
+
// How wide the auth card (sign-in, sign-up, forgot-password) is allowed
|
|
125
|
+
// to grow. It was pinned at 400px, which is narrow for a page that is
|
|
126
|
+
// nothing but that card.
|
|
127
|
+
cardWidth: "--throttle-card-width",
|
|
128
|
+
accountSurface: "--throttle-color-account-surface",
|
|
129
|
+
accountPadding: "--throttle-account-padding",
|
|
130
|
+
shadowCard: "--throttle-shadow-card",
|
|
131
|
+
shadowPopover: "--throttle-shadow-popover",
|
|
132
|
+
transition: "--throttle-transition",
|
|
133
|
+
zModal: "--throttle-z-modal",
|
|
134
|
+
zPopover: "--throttle-z-popover"
|
|
135
|
+
};
|
|
136
|
+
var THROTTLE_ELEMENT_KEYS = [
|
|
137
|
+
// shared primitives
|
|
138
|
+
"card",
|
|
139
|
+
"card.header",
|
|
140
|
+
"card.title",
|
|
141
|
+
"card.description",
|
|
142
|
+
"card.body",
|
|
143
|
+
"card.footer",
|
|
144
|
+
"card.logo",
|
|
145
|
+
"field",
|
|
146
|
+
"field.label",
|
|
147
|
+
"field.input",
|
|
148
|
+
"field.hint",
|
|
149
|
+
"field.error",
|
|
150
|
+
"field.group",
|
|
151
|
+
"button",
|
|
152
|
+
"alert",
|
|
153
|
+
"spinner",
|
|
154
|
+
"skeleton",
|
|
155
|
+
"avatar",
|
|
156
|
+
"badge",
|
|
157
|
+
"emptyState",
|
|
158
|
+
"emptyState.title",
|
|
159
|
+
"emptyState.description",
|
|
160
|
+
"modal.overlay",
|
|
161
|
+
"modal.container",
|
|
162
|
+
"modal.closeButton",
|
|
163
|
+
"dropdown.trigger",
|
|
164
|
+
"dropdown.menu",
|
|
165
|
+
"dropdown.item",
|
|
166
|
+
"dropdown.separator",
|
|
167
|
+
"tabs.list",
|
|
168
|
+
"tabs.tab",
|
|
169
|
+
"table",
|
|
170
|
+
"table.head",
|
|
171
|
+
"table.row",
|
|
172
|
+
"table.cell",
|
|
173
|
+
// auth
|
|
174
|
+
"signIn.root",
|
|
175
|
+
"signIn.title",
|
|
176
|
+
"signIn.logo",
|
|
177
|
+
"signIn.emailField",
|
|
178
|
+
"signIn.passwordField",
|
|
179
|
+
"signIn.submitButton",
|
|
180
|
+
"signIn.forgotLink",
|
|
181
|
+
"signIn.switchLink",
|
|
182
|
+
"signUp.root",
|
|
183
|
+
"signUp.title",
|
|
184
|
+
"signUp.logo",
|
|
185
|
+
"signUp.emailField",
|
|
186
|
+
"signUp.passwordField",
|
|
187
|
+
"signUp.submitButton",
|
|
188
|
+
"signUp.switchLink",
|
|
189
|
+
"forgotPassword.root",
|
|
190
|
+
"forgotPassword.title",
|
|
191
|
+
"forgotPassword.logo",
|
|
192
|
+
"forgotPassword.emailField",
|
|
193
|
+
"forgotPassword.submitButton",
|
|
194
|
+
"forgotPassword.backLink",
|
|
195
|
+
"resetPassword.root",
|
|
196
|
+
"resetPassword.title",
|
|
197
|
+
"resetPassword.logo",
|
|
198
|
+
"resetPassword.passwordField",
|
|
199
|
+
"resetPassword.submitButton",
|
|
200
|
+
"verifyEmail.root",
|
|
201
|
+
"verifyEmail.title",
|
|
202
|
+
"verifyEmail.logo",
|
|
203
|
+
"verifyEmail.resendButton",
|
|
204
|
+
"authFlow.root",
|
|
205
|
+
"authModal.overlay",
|
|
206
|
+
"authModal.container",
|
|
207
|
+
"signInButton.root",
|
|
208
|
+
"signUpButton.root",
|
|
209
|
+
"signOutButton.root",
|
|
210
|
+
"stepUp.root",
|
|
211
|
+
"stepUp.title",
|
|
212
|
+
"stepUp.passwordField",
|
|
213
|
+
"stepUp.submitButton",
|
|
214
|
+
"stepUp.cancelButton",
|
|
215
|
+
"signInPage.root",
|
|
216
|
+
"signInPage.logo",
|
|
217
|
+
"signInPage.footer",
|
|
218
|
+
"signUpPage.root",
|
|
219
|
+
"signUpPage.logo",
|
|
220
|
+
"signUpPage.footer",
|
|
221
|
+
// user button
|
|
222
|
+
"userButton.root",
|
|
223
|
+
"userButton.trigger",
|
|
224
|
+
"userButton.menu",
|
|
225
|
+
"userButton.identity",
|
|
226
|
+
"userButton.email",
|
|
227
|
+
"userButton.badge",
|
|
228
|
+
"userButton.item",
|
|
229
|
+
"userButton.manageButton",
|
|
230
|
+
"userButton.signOutButton",
|
|
231
|
+
"userButton.signOutAllButton",
|
|
232
|
+
// account
|
|
233
|
+
"accountDashboard.root",
|
|
234
|
+
"accountDashboard.tabs",
|
|
235
|
+
"accountDashboard.sidebar",
|
|
236
|
+
"accountDashboard.panel",
|
|
237
|
+
"accountModal.overlay",
|
|
238
|
+
"accountModal.container",
|
|
239
|
+
"accountModal.header",
|
|
240
|
+
"accountModal.title",
|
|
241
|
+
"accountModal.body",
|
|
242
|
+
"verifyBanner.root",
|
|
243
|
+
"verifyBanner.message",
|
|
244
|
+
"verifyBanner.resendButton",
|
|
245
|
+
"verifyBanner.dismissButton",
|
|
246
|
+
"profilePanel.root",
|
|
247
|
+
"profilePanel.form",
|
|
248
|
+
"profilePanel.submitButton",
|
|
249
|
+
"addressesPanel.root",
|
|
250
|
+
"addressesPanel.list",
|
|
251
|
+
"addressesPanel.item",
|
|
252
|
+
"addressesPanel.addButton",
|
|
253
|
+
"addressesPanel.form",
|
|
254
|
+
"addressesPanel.defaultButton",
|
|
255
|
+
"addressesPanel.editButton",
|
|
256
|
+
"addressesPanel.deleteButton",
|
|
257
|
+
"addressesPanel.table",
|
|
258
|
+
"addressesPanel.row",
|
|
259
|
+
"paymentMethodsPanel.root",
|
|
260
|
+
"paymentMethodsPanel.list",
|
|
261
|
+
"paymentMethodsPanel.item",
|
|
262
|
+
"paymentMethodsPanel.defaultButton",
|
|
263
|
+
"paymentMethodsPanel.deleteButton",
|
|
264
|
+
"paymentMethodsPanel.addButton",
|
|
265
|
+
"paymentMethodsPanel.addDialog",
|
|
266
|
+
"orderDetail.addresses",
|
|
267
|
+
"orderDetail.shippingAddress",
|
|
268
|
+
"orderDetail.billingAddress",
|
|
269
|
+
"orderDetail.timeline",
|
|
270
|
+
"orderDetail.payButton",
|
|
271
|
+
"orderDetail.trackButton",
|
|
272
|
+
"orderDetail.reorderButton",
|
|
273
|
+
// Two-column detail layout, shared by the order and invoice pages.
|
|
274
|
+
"orderDetail.layout",
|
|
275
|
+
"orderDetail.main",
|
|
276
|
+
"orderDetail.rail",
|
|
277
|
+
"orderDetail.cancelButton",
|
|
278
|
+
"orderDetail.returnButton",
|
|
279
|
+
"orderDetail.invoiceButton",
|
|
280
|
+
"orderDetail.subscriptionButton",
|
|
281
|
+
"orderDetail.actions",
|
|
282
|
+
"invoiceDetail.layout",
|
|
283
|
+
"invoiceDetail.main",
|
|
284
|
+
"invoiceDetail.rail",
|
|
285
|
+
"invoiceDetail.lineItems",
|
|
286
|
+
"invoiceDetail.lineItem",
|
|
287
|
+
"invoiceDetail.orderLink",
|
|
288
|
+
"invoiceDetail.subscriptionLink",
|
|
289
|
+
"invoiceDetail.payButton",
|
|
290
|
+
"invoiceDetail.actions",
|
|
291
|
+
"fieldRow",
|
|
292
|
+
"confirm.header",
|
|
293
|
+
"confirm.title",
|
|
294
|
+
"confirm.body",
|
|
295
|
+
"confirm.confirmButton",
|
|
296
|
+
"confirm.cancelButton",
|
|
297
|
+
"paymentMethodsPanel.table",
|
|
298
|
+
"paymentMethodsPanel.row",
|
|
299
|
+
"ordersPanel.root",
|
|
300
|
+
"ordersPanel.table",
|
|
301
|
+
"ordersPanel.row",
|
|
302
|
+
"ordersPanel.list",
|
|
303
|
+
"ordersPanel.item",
|
|
304
|
+
"ordersPanel.viewButton",
|
|
305
|
+
"orderDetail.root",
|
|
306
|
+
"orderDetail.backButton",
|
|
307
|
+
"orderDetail.lineItems",
|
|
308
|
+
"orderDetail.lineItem",
|
|
309
|
+
"orderDetail.totals",
|
|
310
|
+
"invoicesPanel.root",
|
|
311
|
+
"invoicesPanel.table",
|
|
312
|
+
"invoicesPanel.row",
|
|
313
|
+
"invoicesPanel.list",
|
|
314
|
+
"invoicesPanel.item",
|
|
315
|
+
"invoicesPanel.viewButton",
|
|
316
|
+
"invoiceDetail.root",
|
|
317
|
+
"invoiceDetail.backButton",
|
|
318
|
+
"invoiceDetail.downloadButton",
|
|
319
|
+
"invoiceDetail.totals",
|
|
320
|
+
"subscriptionsPanel.root",
|
|
321
|
+
"subscriptionsPanel.list",
|
|
322
|
+
"subscriptionsPanel.item",
|
|
323
|
+
"subscriptionsPanel.cancelButton",
|
|
324
|
+
"subscriptionsPanel.cancelNowButton",
|
|
325
|
+
"subscriptionsPanel.pauseButton",
|
|
326
|
+
"subscriptionsPanel.resumeButton",
|
|
327
|
+
"subscriptionsPanel.changeAddressButton",
|
|
328
|
+
"subscriptionsPanel.addressForm",
|
|
329
|
+
"subscriptionsPanel.table",
|
|
330
|
+
"subscriptionsPanel.row",
|
|
331
|
+
"securityPanel.root",
|
|
332
|
+
"securityPanel.passwordForm",
|
|
333
|
+
"securityPanel.passwordButton",
|
|
334
|
+
"securityPanel.sessionList",
|
|
335
|
+
"securityPanel.sessionItem",
|
|
336
|
+
"securityPanel.revokeButton",
|
|
337
|
+
"securityPanel.revokeAllButton",
|
|
338
|
+
"securityPanel.table",
|
|
339
|
+
"securityPanel.row"
|
|
340
|
+
];
|
|
341
|
+
var Ctx = createContext(null);
|
|
342
|
+
function AppearanceProvider(props) {
|
|
343
|
+
return /* @__PURE__ */ jsx(Ctx.Provider, { value: props.value, children: props.children });
|
|
344
|
+
}
|
|
345
|
+
function useAppearance(local) {
|
|
346
|
+
const provider = useContext(Ctx);
|
|
347
|
+
return useMemo(() => {
|
|
348
|
+
const theme = local?.theme ?? provider?.theme ?? "light";
|
|
349
|
+
const variables = { ...provider?.variables, ...local?.variables };
|
|
350
|
+
const elements = { ...provider?.elements, ...local?.elements };
|
|
351
|
+
const slots = { ...provider?.components, ...local?.components };
|
|
352
|
+
const style = {};
|
|
353
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
354
|
+
const property = VARIABLE_PROPERTY[key];
|
|
355
|
+
if (property && value != null) style[property] = value;
|
|
356
|
+
}
|
|
357
|
+
warnOnUnreadablePrimary(variables, theme);
|
|
358
|
+
return {
|
|
359
|
+
theme,
|
|
360
|
+
rootProps: { className: "throttle-root", style, "data-throttle-theme": theme },
|
|
361
|
+
el(key, base) {
|
|
362
|
+
const extra = elements[key];
|
|
363
|
+
return {
|
|
364
|
+
className: [base, extra].filter(Boolean).join(" ") || void 0,
|
|
365
|
+
"data-throttle-element": key
|
|
366
|
+
};
|
|
367
|
+
},
|
|
368
|
+
slots
|
|
369
|
+
};
|
|
370
|
+
}, [provider, local]);
|
|
371
|
+
}
|
|
372
|
+
function useAppearanceProviderValue(local) {
|
|
373
|
+
const provider = useContext(Ctx);
|
|
374
|
+
return useMemo(
|
|
375
|
+
() => ({
|
|
376
|
+
theme: local?.theme ?? provider?.theme,
|
|
377
|
+
variables: { ...provider?.variables, ...local?.variables },
|
|
378
|
+
elements: { ...provider?.elements, ...local?.elements },
|
|
379
|
+
components: { ...provider?.components, ...local?.components }
|
|
380
|
+
}),
|
|
381
|
+
[provider, local]
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/react/components/localization.tsx
|
|
386
|
+
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
387
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
388
|
+
var enUS = {
|
|
389
|
+
locale: "en-US",
|
|
390
|
+
common: {
|
|
391
|
+
save: "Save",
|
|
392
|
+
cancel: "Cancel",
|
|
393
|
+
delete: "Delete",
|
|
394
|
+
edit: "Edit",
|
|
395
|
+
back: "Back",
|
|
396
|
+
retry: "Try again",
|
|
397
|
+
loading: "Loading\u2026",
|
|
398
|
+
close: "Close",
|
|
399
|
+
confirm: "Are you sure?",
|
|
400
|
+
// The dismissive half of a confirmation. "Cancel" is ambiguous next to
|
|
401
|
+
// "Cancel subscription" — one dismisses the dialog, the other ends the plan.
|
|
402
|
+
keepIt: "Keep it",
|
|
403
|
+
somethingWentWrong: "Something went wrong.",
|
|
404
|
+
actions: "Actions",
|
|
405
|
+
view: "View"
|
|
406
|
+
},
|
|
407
|
+
signIn: {
|
|
408
|
+
title: "Sign in",
|
|
409
|
+
description: "Sign in to your account.",
|
|
410
|
+
email: "Email",
|
|
411
|
+
password: "Password",
|
|
412
|
+
submitButton: "Sign in",
|
|
413
|
+
forgotLink: "Forgot your password?",
|
|
414
|
+
switchPrompt: "Don't have an account?",
|
|
415
|
+
switchLink: "Sign up"
|
|
416
|
+
},
|
|
417
|
+
signUp: {
|
|
418
|
+
title: "Create your account",
|
|
419
|
+
description: "",
|
|
420
|
+
email: "Email",
|
|
421
|
+
password: "Password",
|
|
422
|
+
submitButton: "Create account",
|
|
423
|
+
switchPrompt: "Already have an account?",
|
|
424
|
+
switchLink: "Sign in",
|
|
425
|
+
// Deliberately conditional. Registering onto an address that already has
|
|
426
|
+
// an account returns the same `pending` status and sends no email, so
|
|
427
|
+
// asserting one was sent would be false for that buyer.
|
|
428
|
+
pendingNotice: "If that email is new, check your inbox for a link to finish signing up."
|
|
429
|
+
},
|
|
430
|
+
forgotPassword: {
|
|
431
|
+
title: "Reset your password",
|
|
432
|
+
description: "We will email you a link if an account exists.",
|
|
433
|
+
email: "Email",
|
|
434
|
+
submitButton: "Send reset link",
|
|
435
|
+
sentNotice: "If an account exists for that email, a reset link is on its way.",
|
|
436
|
+
backLink: "Back to sign in"
|
|
437
|
+
},
|
|
438
|
+
resetPassword: {
|
|
439
|
+
title: "Choose a new password",
|
|
440
|
+
password: "New password",
|
|
441
|
+
submitButton: "Update password",
|
|
442
|
+
doneNotice: "Your password has been updated.",
|
|
443
|
+
missingToken: "That link is missing its token. Request a new one."
|
|
444
|
+
},
|
|
445
|
+
verifyEmail: {
|
|
446
|
+
title: "Verify your email",
|
|
447
|
+
verifying: "Confirming your email\u2026",
|
|
448
|
+
success: "Your email is verified.",
|
|
449
|
+
resendButton: "Resend the link",
|
|
450
|
+
resentNotice: "We sent a new link.",
|
|
451
|
+
instructions: "Check your inbox for a verification link."
|
|
452
|
+
},
|
|
453
|
+
stepUp: {
|
|
454
|
+
title: "Confirm your password",
|
|
455
|
+
description: "For your security, please re-enter your password to continue.",
|
|
456
|
+
password: "Password",
|
|
457
|
+
submitButton: "Confirm",
|
|
458
|
+
cancelButton: "Cancel"
|
|
459
|
+
},
|
|
460
|
+
userButton: {
|
|
461
|
+
manage: "Manage account",
|
|
462
|
+
signOut: "Sign out",
|
|
463
|
+
signOutAll: "Sign out of all devices",
|
|
464
|
+
verified: "Verified",
|
|
465
|
+
unverified: "Unverified",
|
|
466
|
+
openMenu: "Account menu"
|
|
467
|
+
},
|
|
468
|
+
verifyBanner: {
|
|
469
|
+
message: "Verify your email to see your full account history.",
|
|
470
|
+
resendButton: "Resend link",
|
|
471
|
+
dismissButton: "Dismiss"
|
|
472
|
+
},
|
|
473
|
+
account: {
|
|
474
|
+
title: "Your account",
|
|
475
|
+
tabs: {
|
|
476
|
+
profile: "Profile",
|
|
477
|
+
addresses: "Addresses",
|
|
478
|
+
paymentMethods: "Saved cards",
|
|
479
|
+
orders: "Orders",
|
|
480
|
+
invoices: "Invoices",
|
|
481
|
+
subscriptions: "Subscriptions",
|
|
482
|
+
security: "Security"
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
profile: {
|
|
486
|
+
firstName: "First name",
|
|
487
|
+
lastName: "Last name",
|
|
488
|
+
phone: "Phone",
|
|
489
|
+
company: "Company",
|
|
490
|
+
acceptsMarketing: "Send me product news and offers",
|
|
491
|
+
email: "Email",
|
|
492
|
+
emailReadOnly: "Your email address cannot be changed here.",
|
|
493
|
+
saveButton: "Save changes",
|
|
494
|
+
savedNotice: "Profile updated."
|
|
495
|
+
},
|
|
496
|
+
addresses: {
|
|
497
|
+
addButton: "Add address",
|
|
498
|
+
defaultBadge: "Default",
|
|
499
|
+
makeDefault: "Make default",
|
|
500
|
+
deleteConfirmAction: "Delete address",
|
|
501
|
+
empty: "No saved addresses",
|
|
502
|
+
emptyDescription: "Addresses you use at checkout can be saved here.",
|
|
503
|
+
label: "Label",
|
|
504
|
+
firstName: "First name",
|
|
505
|
+
lastName: "Last name",
|
|
506
|
+
company: "Company",
|
|
507
|
+
addressLine1: "Address",
|
|
508
|
+
addressLine2: "Apartment, suite, etc.",
|
|
509
|
+
city: "City",
|
|
510
|
+
stateProvince: "State / Province",
|
|
511
|
+
postalCode: "Postal code",
|
|
512
|
+
countryCode: "Country code",
|
|
513
|
+
phone: "Phone",
|
|
514
|
+
deleteConfirm: "Delete this address?",
|
|
515
|
+
columnAddress: "Address"
|
|
516
|
+
},
|
|
517
|
+
paymentMethods: {
|
|
518
|
+
empty: "No saved cards",
|
|
519
|
+
emptyDescription: "Add a card here, or one will be saved when you check out.",
|
|
520
|
+
addButton: "Add card",
|
|
521
|
+
addTitle: "Add a card",
|
|
522
|
+
// Shown whenever the panel is empty with no error and the buyer is not
|
|
523
|
+
// verified — including simply being signed out — matching the copy
|
|
524
|
+
// orders/invoices/subscriptions already show in that state.
|
|
525
|
+
gatedDescription: "Verify your email to see your saved cards.",
|
|
526
|
+
defaultBadge: "Default",
|
|
527
|
+
makeDefault: "Make default",
|
|
528
|
+
expires: "Expires",
|
|
529
|
+
deleteConfirm: "Remove this card?",
|
|
530
|
+
deleteConfirmAction: "Remove card",
|
|
531
|
+
columnCard: "Card"
|
|
532
|
+
},
|
|
533
|
+
orders: {
|
|
534
|
+
shipTo: "Ship to",
|
|
535
|
+
billTo: "Bill to",
|
|
536
|
+
history: "History",
|
|
537
|
+
placed: "Order placed",
|
|
538
|
+
completed: "Completed",
|
|
539
|
+
cancelledAt: "Cancelled",
|
|
540
|
+
closedAt: "Closed",
|
|
541
|
+
payNow: "Pay now",
|
|
542
|
+
track: "Track shipment",
|
|
543
|
+
reorder: "Order again",
|
|
544
|
+
cancelOrder: "Cancel order",
|
|
545
|
+
cancelOrderConfirm: "Cancel this order?",
|
|
546
|
+
cancelOrderBody: "This cannot be undone. Nothing has been charged, so there is nothing to refund.",
|
|
547
|
+
cancelOrderAction: "Cancel order",
|
|
548
|
+
requestReturn: "Request a return",
|
|
549
|
+
requestReturnConfirm: "Request a return for this order?",
|
|
550
|
+
requestReturnBody: "The merchant reviews your request and will be in touch. Nothing is refunded until they approve it.",
|
|
551
|
+
requestReturnAction: "Send request",
|
|
552
|
+
returnRequested: "Return requested. The merchant will be in touch.",
|
|
553
|
+
downloadInvoice: "Download invoice",
|
|
554
|
+
summary: "Summary",
|
|
555
|
+
empty: "No orders yet",
|
|
556
|
+
emptyDescription: "Orders you place will appear here.",
|
|
557
|
+
gatedDescription: "Verify your email to see your order history.",
|
|
558
|
+
number: "Order",
|
|
559
|
+
date: "Date",
|
|
560
|
+
status: "Status",
|
|
561
|
+
total: "Total",
|
|
562
|
+
/**
|
|
563
|
+
* Display copy for `order.status`. Mirrors ORDER_STATUS_META in
|
|
564
|
+
* `@commerce/orders`, which is the server's single source of status copy —
|
|
565
|
+
* copied rather than imported because this package ships with no runtime
|
|
566
|
+
* dependencies and is a client for the wire API, not for server packages.
|
|
567
|
+
* If a status is added there, add it here.
|
|
568
|
+
*/
|
|
569
|
+
statusLabels: {
|
|
570
|
+
draft: "Draft",
|
|
571
|
+
pending: "Pending",
|
|
572
|
+
processing: "Processing",
|
|
573
|
+
partiallyFulfilled: "Partially fulfilled",
|
|
574
|
+
fulfilled: "Fulfilled",
|
|
575
|
+
cancelled: "Cancelled",
|
|
576
|
+
closed: "Closed"
|
|
577
|
+
},
|
|
578
|
+
/**
|
|
579
|
+
* The money half of the label, shown as a second badge beside the
|
|
580
|
+
* lifecycle one. `captured` is deliberately empty: paid in full is the
|
|
581
|
+
* assumed happy path, and announcing it on every settled order is noise.
|
|
582
|
+
*/
|
|
583
|
+
paymentLabels: {
|
|
584
|
+
pending: "Awaiting payment",
|
|
585
|
+
authorized: "Authorized",
|
|
586
|
+
captured: "",
|
|
587
|
+
partiallyPaid: "Partially paid",
|
|
588
|
+
partiallyRefunded: "Partially refunded",
|
|
589
|
+
refunded: "Refunded",
|
|
590
|
+
failed: "Payment failed",
|
|
591
|
+
voided: "Voided",
|
|
592
|
+
processing: "Payment processing",
|
|
593
|
+
disputed: "Disputed",
|
|
594
|
+
expired: "Authorization expired"
|
|
595
|
+
},
|
|
596
|
+
items: "Items",
|
|
597
|
+
subtotal: "Subtotal",
|
|
598
|
+
discount: "Discount",
|
|
599
|
+
shipping: "Shipping",
|
|
600
|
+
tax: "Tax",
|
|
601
|
+
quantity: "Qty",
|
|
602
|
+
shippingAddress: "Shipping address",
|
|
603
|
+
billingAddress: "Billing address",
|
|
604
|
+
viewSubscription: "View subscription",
|
|
605
|
+
viewSubscriptionFor: "View subscription for"
|
|
606
|
+
},
|
|
607
|
+
invoices: {
|
|
608
|
+
empty: "No invoices yet",
|
|
609
|
+
emptyDescription: "Invoices will appear here.",
|
|
610
|
+
gatedDescription: "Verify your email to see your invoices.",
|
|
611
|
+
number: "Invoice",
|
|
612
|
+
issued: "Issued",
|
|
613
|
+
due: "Due",
|
|
614
|
+
status: "Status",
|
|
615
|
+
total: "Total",
|
|
616
|
+
refunded: "Refunded",
|
|
617
|
+
period: "Period",
|
|
618
|
+
downloadButton: "Download PDF",
|
|
619
|
+
items: "Items",
|
|
620
|
+
summary: "Summary",
|
|
621
|
+
viewOrder: "View order",
|
|
622
|
+
viewSubscription: "View subscription",
|
|
623
|
+
payButton: "Pay this invoice",
|
|
624
|
+
billedTo: "Billed to",
|
|
625
|
+
/** Shown when an invoice bills less than the order, matching the PDF. */
|
|
626
|
+
orderTotal: "Order total",
|
|
627
|
+
amountPaid: "Amount paid",
|
|
628
|
+
waived: "Waived",
|
|
629
|
+
amountDue: "Amount due",
|
|
630
|
+
balanceDue: "Balance due",
|
|
631
|
+
/** Display copy for `invoice.status` — the wire values are paid|open|void. */
|
|
632
|
+
statusLabels: { paid: "Paid", open: "Open", void: "Void" },
|
|
633
|
+
downloadUnavailable: "This invoice is not available to download right now."
|
|
634
|
+
},
|
|
635
|
+
subscriptions: {
|
|
636
|
+
cancelConfirmAction: "Cancel plan",
|
|
637
|
+
pauseConfirm: "Pause this subscription?",
|
|
638
|
+
resumeConfirm: "Resume this subscription?",
|
|
639
|
+
// Distinct from cancelConfirm: ending it now is not the same promise as
|
|
640
|
+
// letting it run to the end of a period the buyer already paid for.
|
|
641
|
+
cancelNowConfirm: "End this subscription immediately?",
|
|
642
|
+
empty: "No subscriptions",
|
|
643
|
+
emptyDescription: "Subscriptions will appear here.",
|
|
644
|
+
gatedDescription: "Verify your email to see your subscriptions.",
|
|
645
|
+
renewsOn: "Renews on",
|
|
646
|
+
endsOn: "Ends on",
|
|
647
|
+
cancelledOn: "Cancelled on",
|
|
648
|
+
trialEnds: "Trial ends",
|
|
649
|
+
installmentProgress: "Payment {paid} of {total}",
|
|
650
|
+
installmentTotal: "{amount} total",
|
|
651
|
+
installmentNext: "Next {amount} on {date}",
|
|
652
|
+
/** Spec §5: a varied schedule's dated list; dates are estimates, hence "~". */
|
|
653
|
+
upcomingPayments: "Upcoming payments",
|
|
654
|
+
upcomingPayment: "{amount} \xB7 ~{date}",
|
|
655
|
+
cancelButton: "Cancel",
|
|
656
|
+
cancelAtPeriodEnd: "Cancel at period end",
|
|
657
|
+
cancelNow: "Cancel immediately",
|
|
658
|
+
pauseButton: "Pause",
|
|
659
|
+
resumeButton: "Resume",
|
|
660
|
+
/** A physical subscription's ship-to, and the control that changes it. */
|
|
661
|
+
shipsTo: "Ships to",
|
|
662
|
+
changeAddress: "Change address",
|
|
663
|
+
changeAddressTitle: "Change shipping address",
|
|
664
|
+
changeAddressHint: "Your next box ships here. Orders already placed keep their address.",
|
|
665
|
+
cancelConfirm: "Cancel this subscription?",
|
|
666
|
+
cancelScheduled: "Cancels at the end of the period",
|
|
667
|
+
columnPlan: "Plan",
|
|
668
|
+
columnPrice: "Price",
|
|
669
|
+
columnRenews: "Renews",
|
|
670
|
+
columnStatus: "Status",
|
|
671
|
+
/**
|
|
672
|
+
* Display copy for `subscription.status`. Mirrors SubscriptionStatus in
|
|
673
|
+
* `@commerce/subscriptions`; copied rather than imported because this
|
|
674
|
+
* package has no runtime dependencies. Without it a buyer in dunning saw
|
|
675
|
+
* the literal pill `past_due`. `completed` is an installment plan that
|
|
676
|
+
* has taken its last payment.
|
|
677
|
+
*/
|
|
678
|
+
statusLabels: {
|
|
679
|
+
active: "Active",
|
|
680
|
+
paused: "Paused",
|
|
681
|
+
cancelled: "Cancelled",
|
|
682
|
+
pastDue: "Payment overdue",
|
|
683
|
+
trialing: "Trial",
|
|
684
|
+
unpaid: "Unpaid",
|
|
685
|
+
completed: "Paid in full"
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
security: {
|
|
689
|
+
passwordTitle: "Change password",
|
|
690
|
+
newPassword: "New password",
|
|
691
|
+
changeButton: "Update password",
|
|
692
|
+
changedNotice: "Password updated.",
|
|
693
|
+
sessionsTitle: "Devices",
|
|
694
|
+
thisDevice: "This device",
|
|
695
|
+
lastActive: "Last active",
|
|
696
|
+
revokeButton: "Sign out",
|
|
697
|
+
revokeAllButton: "Sign out of all other devices",
|
|
698
|
+
sessionsEmpty: "No other devices",
|
|
699
|
+
columnDevice: "Device"
|
|
700
|
+
},
|
|
701
|
+
errors: {}
|
|
702
|
+
};
|
|
703
|
+
function isPlainObject(value) {
|
|
704
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
705
|
+
}
|
|
706
|
+
function deepMerge(base, over, path = "") {
|
|
707
|
+
if (!over) return base;
|
|
708
|
+
const out = Array.isArray(base) ? [...base] : { ...base };
|
|
709
|
+
for (const [key, value] of Object.entries(over)) {
|
|
710
|
+
if (value == null) continue;
|
|
711
|
+
const current = base[key];
|
|
712
|
+
const keyPath = path ? `${path}.${key}` : key;
|
|
713
|
+
const currentIsObject = isPlainObject(current);
|
|
714
|
+
const valueIsObject = isPlainObject(value);
|
|
715
|
+
if (current !== void 0 && currentIsObject !== valueIsObject) {
|
|
716
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
717
|
+
console.warn(
|
|
718
|
+
`[useCopy] ignoring localization override at "${keyPath}": expected ${currentIsObject ? "an object" : "a string"}, got ${valueIsObject ? "an object" : typeof value}`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
out[key] = currentIsObject && valueIsObject ? deepMerge(current, value, keyPath) : value;
|
|
724
|
+
}
|
|
725
|
+
return out;
|
|
726
|
+
}
|
|
727
|
+
var Ctx2 = createContext2(null);
|
|
728
|
+
function LocalizationProvider(props) {
|
|
729
|
+
return /* @__PURE__ */ jsx2(Ctx2.Provider, { value: props.value, children: props.children });
|
|
730
|
+
}
|
|
731
|
+
function useCopy(local) {
|
|
732
|
+
const provider = useContext2(Ctx2);
|
|
733
|
+
return useMemo2(() => {
|
|
734
|
+
const t = deepMerge(deepMerge(enUS, provider), local);
|
|
735
|
+
return {
|
|
736
|
+
t,
|
|
737
|
+
locale: t.locale,
|
|
738
|
+
/**
|
|
739
|
+
* A merchant's `errors` map wins; otherwise the shipped mapper decides.
|
|
740
|
+
* `messageForError` stays the floor because it encodes two deliberate
|
|
741
|
+
* non-passthroughs — `invalid_credentials` stays vague to avoid account
|
|
742
|
+
* enumeration, and unknown codes stay generic.
|
|
743
|
+
*/
|
|
744
|
+
errorMessage(err) {
|
|
745
|
+
const code = err?.code;
|
|
746
|
+
const override = code ? t.errors[code] : void 0;
|
|
747
|
+
return override ?? messageForError(err);
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
}, [provider, local]);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/react/components/primitives/Modal.tsx
|
|
754
|
+
import { useEffect, useRef, useState } from "react";
|
|
755
|
+
import { createPortal } from "react-dom";
|
|
756
|
+
|
|
757
|
+
// src/react/components/surface.tsx
|
|
758
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
759
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
760
|
+
var Ctx3 = createContext3("standalone");
|
|
761
|
+
function SurfaceProvider(props) {
|
|
762
|
+
return /* @__PURE__ */ jsx3(Ctx3.Provider, { value: props.value, children: props.children });
|
|
763
|
+
}
|
|
764
|
+
function useSurface() {
|
|
765
|
+
return useContext3(Ctx3);
|
|
766
|
+
}
|
|
767
|
+
function useCardClass() {
|
|
768
|
+
return useSurface() === "modal" ? "throttle-card throttle-card--bare" : "throttle-card";
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/react/components/primitives/Modal.tsx
|
|
772
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
773
|
+
var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
774
|
+
function Modal(props) {
|
|
775
|
+
const a = useAppearance();
|
|
776
|
+
const { t } = useCopy();
|
|
777
|
+
const panel = useRef(null);
|
|
778
|
+
const returnFocusTo = useRef(null);
|
|
779
|
+
const [mounted, setMounted] = useState(false);
|
|
780
|
+
useEffect(() => {
|
|
781
|
+
setMounted(true);
|
|
782
|
+
}, []);
|
|
783
|
+
const onCloseRef = useRef(props.onClose);
|
|
784
|
+
useEffect(() => {
|
|
785
|
+
onCloseRef.current = props.onClose;
|
|
786
|
+
});
|
|
787
|
+
useEffect(() => {
|
|
788
|
+
if (!props.open || !mounted) return;
|
|
789
|
+
returnFocusTo.current = props.restoreFocusTo ?? document.activeElement;
|
|
790
|
+
const previousOverflow = document.body.style.overflow;
|
|
791
|
+
document.body.style.overflow = "hidden";
|
|
792
|
+
panel.current?.focus();
|
|
793
|
+
const onKeyDown = (e) => {
|
|
794
|
+
if (e.key === "Escape") {
|
|
795
|
+
onCloseRef.current();
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if (e.key !== "Tab" || !panel.current) return;
|
|
799
|
+
const items = Array.from(panel.current.querySelectorAll(FOCUSABLE));
|
|
800
|
+
if (items.length === 0) {
|
|
801
|
+
e.preventDefault();
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const first = items[0];
|
|
805
|
+
const last = items[items.length - 1];
|
|
806
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
807
|
+
e.preventDefault();
|
|
808
|
+
last.focus();
|
|
809
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
810
|
+
e.preventDefault();
|
|
811
|
+
first.focus();
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
document.addEventListener("keydown", onKeyDown);
|
|
815
|
+
return () => {
|
|
816
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
817
|
+
document.body.style.overflow = previousOverflow;
|
|
818
|
+
returnFocusTo.current?.focus?.();
|
|
819
|
+
};
|
|
820
|
+
}, [props.open, mounted]);
|
|
821
|
+
if (!props.open || !mounted) return null;
|
|
822
|
+
return createPortal(
|
|
823
|
+
/* @__PURE__ */ jsx4("div", { ...a.rootProps, children: /* @__PURE__ */ jsx4(
|
|
824
|
+
"div",
|
|
825
|
+
{
|
|
826
|
+
...a.el(props.overlayKey ?? "modal.overlay", "throttle-modal__overlay"),
|
|
827
|
+
"data-testid": "throttle-modal-overlay",
|
|
828
|
+
onClick: (e) => {
|
|
829
|
+
if (e.target === e.currentTarget) props.onClose();
|
|
830
|
+
},
|
|
831
|
+
children: /* @__PURE__ */ jsxs(
|
|
832
|
+
"div",
|
|
833
|
+
{
|
|
834
|
+
...a.el(props.containerKey ?? "modal.container", props.size === "compact" ? "throttle-modal__container throttle-modal__container--compact" : "throttle-modal__container"),
|
|
835
|
+
ref: panel,
|
|
836
|
+
role: "dialog",
|
|
837
|
+
"aria-modal": "true",
|
|
838
|
+
"aria-labelledby": props.labelledBy,
|
|
839
|
+
tabIndex: -1,
|
|
840
|
+
children: [
|
|
841
|
+
/* @__PURE__ */ jsx4(
|
|
842
|
+
"button",
|
|
843
|
+
{
|
|
844
|
+
...a.el("modal.closeButton", "throttle-modal__close"),
|
|
845
|
+
type: "button",
|
|
846
|
+
"aria-label": t.common.close,
|
|
847
|
+
onClick: props.onClose,
|
|
848
|
+
children: "\xD7"
|
|
849
|
+
}
|
|
850
|
+
),
|
|
851
|
+
/* @__PURE__ */ jsx4(SurfaceProvider, { value: "modal", children: props.children })
|
|
852
|
+
]
|
|
853
|
+
}
|
|
854
|
+
)
|
|
855
|
+
}
|
|
856
|
+
) }),
|
|
857
|
+
document.body
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/react/components/primitives/Confirm.tsx
|
|
862
|
+
import { createContext as createContext4, useCallback, useContext as useContext4, useEffect as useEffect2, useId as useId2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
|
|
863
|
+
|
|
864
|
+
// src/react/components/primitives/Spinner.tsx
|
|
865
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
866
|
+
function Spinner({ centred } = {}) {
|
|
867
|
+
const a = useAppearance();
|
|
868
|
+
const Slot = a.slots.Spinner;
|
|
869
|
+
if (Slot) return /* @__PURE__ */ jsx5(Slot, {});
|
|
870
|
+
const cls = `throttle-spinner${centred ? " throttle-spinner--centred" : ""}`;
|
|
871
|
+
return /* @__PURE__ */ jsx5("span", { ...a.el("spinner", cls), "aria-hidden": "true" });
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/react/components/primitives/Button.tsx
|
|
875
|
+
import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
876
|
+
function Button({ variant = "primary", size = "default", elementKey, loading, children, ...rest }) {
|
|
877
|
+
const a = useAppearance();
|
|
878
|
+
const Slot = a.slots.Button;
|
|
879
|
+
if (Slot) return /* @__PURE__ */ jsx6(Slot, { variant, loading, ...rest, children });
|
|
880
|
+
const base = `throttle-button throttle-button--${variant}${size === "xs" ? " throttle-button--xs" : ""}${loading ? " throttle-button--loading" : ""}`;
|
|
881
|
+
return /* @__PURE__ */ jsxs2(
|
|
882
|
+
"button",
|
|
883
|
+
{
|
|
884
|
+
type: "button",
|
|
885
|
+
...rest,
|
|
886
|
+
...a.el(elementKey ?? "button", base),
|
|
887
|
+
disabled: rest.disabled || loading,
|
|
888
|
+
"aria-busy": loading ? true : void 0,
|
|
889
|
+
children: [
|
|
890
|
+
loading ? /* @__PURE__ */ jsx6(Spinner, { centred: true }) : null,
|
|
891
|
+
/* @__PURE__ */ jsx6("span", { className: `throttle-button__label${loading ? " throttle-button__label--hidden" : ""}`, children })
|
|
892
|
+
]
|
|
893
|
+
}
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/react/components/primitives/Field.tsx
|
|
898
|
+
import { Children, Fragment, useId } from "react";
|
|
899
|
+
import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
900
|
+
function Field(props) {
|
|
901
|
+
const a = useAppearance();
|
|
902
|
+
const base = useId();
|
|
903
|
+
const inputId = `${base}-input`;
|
|
904
|
+
const hintId = `${base}-hint`;
|
|
905
|
+
const errorId = `${base}-error`;
|
|
906
|
+
const describedBy = [props.hint ? hintId : null, props.error ? errorId : null].filter(Boolean).join(" ");
|
|
907
|
+
const label = /* @__PURE__ */ jsx7("label", { ...a.el("field.label", "throttle-field__label"), htmlFor: inputId, children: props.label });
|
|
908
|
+
return /* @__PURE__ */ jsxs3("div", { ...a.el(props.elementKey ?? "field", "throttle-field"), children: [
|
|
909
|
+
props.labelExtra ? /* @__PURE__ */ jsxs3("div", { className: "throttle-field__labelrow", children: [
|
|
910
|
+
label,
|
|
911
|
+
props.labelExtra
|
|
912
|
+
] }) : label,
|
|
913
|
+
/* @__PURE__ */ jsx7(
|
|
914
|
+
"input",
|
|
915
|
+
{
|
|
916
|
+
...a.el("field.input", `throttle-field__input${props.grouped ? " throttle-field__input--grouped" : ""}`),
|
|
917
|
+
id: inputId,
|
|
918
|
+
name: props.name,
|
|
919
|
+
type: props.type ?? "text",
|
|
920
|
+
value: props.value,
|
|
921
|
+
required: props.required,
|
|
922
|
+
disabled: props.disabled,
|
|
923
|
+
autoComplete: props.autoComplete,
|
|
924
|
+
"aria-describedby": describedBy || void 0,
|
|
925
|
+
"aria-invalid": props.error ? true : void 0,
|
|
926
|
+
onChange: (e) => props.onChange(e.target.value)
|
|
927
|
+
}
|
|
928
|
+
),
|
|
929
|
+
props.hint ? /* @__PURE__ */ jsx7("span", { ...a.el("field.hint", "throttle-field__hint"), id: hintId, children: props.hint }) : null,
|
|
930
|
+
/* @__PURE__ */ jsx7("span", { ...a.el("field.error", "throttle-field__error"), id: errorId, role: "alert", children: props.error ?? "" })
|
|
931
|
+
] });
|
|
932
|
+
}
|
|
933
|
+
function FieldGroup(props) {
|
|
934
|
+
const a = useAppearance();
|
|
935
|
+
const items = Children.toArray(props.children).filter(Boolean);
|
|
936
|
+
return /* @__PURE__ */ jsx7("div", { ...a.el(props.elementKey ?? "field.group", "throttle-field-group"), children: items.map((child, i) => /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
937
|
+
i > 0 ? /* @__PURE__ */ jsx7("div", { className: "throttle-field-group__divider" }) : null,
|
|
938
|
+
/* @__PURE__ */ jsx7("div", { className: "throttle-field-group__item", children: child })
|
|
939
|
+
] }, i)) });
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/react/components/primitives/Confirm.tsx
|
|
943
|
+
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
944
|
+
var ConfirmContext = createContext4(null);
|
|
945
|
+
function ConfirmProvider({
|
|
946
|
+
children,
|
|
947
|
+
stepUpOpen = false,
|
|
948
|
+
onStepUpResolve
|
|
949
|
+
}) {
|
|
950
|
+
const [request, setRequest] = useState2(null);
|
|
951
|
+
const [password, setPassword] = useState2("");
|
|
952
|
+
const { t } = useCopy();
|
|
953
|
+
const a = useAppearance();
|
|
954
|
+
const titleId = useId2();
|
|
955
|
+
const settle = useRef2(null);
|
|
956
|
+
useEffect2(() => {
|
|
957
|
+
if (!stepUpOpen) setPassword("");
|
|
958
|
+
}, [stepUpOpen]);
|
|
959
|
+
const answer = useCallback((value) => {
|
|
960
|
+
settle.current?.(value);
|
|
961
|
+
settle.current = null;
|
|
962
|
+
setRequest(null);
|
|
963
|
+
}, []);
|
|
964
|
+
const ask = useCallback((next) => {
|
|
965
|
+
settle.current?.(false);
|
|
966
|
+
setRequest(next);
|
|
967
|
+
return new Promise((resolve) => {
|
|
968
|
+
settle.current = resolve;
|
|
969
|
+
});
|
|
970
|
+
}, []);
|
|
971
|
+
const showStepUp = stepUpOpen && Boolean(onStepUpResolve);
|
|
972
|
+
const open = showStepUp || request !== null;
|
|
973
|
+
const close = () => {
|
|
974
|
+
if (showStepUp) onStepUpResolve(null);
|
|
975
|
+
else answer(false);
|
|
976
|
+
};
|
|
977
|
+
return /* @__PURE__ */ jsxs4(ConfirmContext.Provider, { value: ask, children: [
|
|
978
|
+
children,
|
|
979
|
+
open ? /* @__PURE__ */ jsx8(Modal, { open: true, onClose: close, labelledBy: titleId, size: "compact", children: showStepUp ? /* @__PURE__ */ jsxs4("form", { onSubmit: (e) => {
|
|
980
|
+
e.preventDefault();
|
|
981
|
+
onStepUpResolve(password);
|
|
982
|
+
}, children: [
|
|
983
|
+
/* @__PURE__ */ jsx8("div", { ...a.el("confirm.header", "throttle-modal__header"), children: /* @__PURE__ */ jsx8("h2", { id: titleId, ...a.el("stepUp.title", "throttle-modal__title"), children: t.stepUp.title }) }),
|
|
984
|
+
/* @__PURE__ */ jsxs4("div", { ...a.el("confirm.body", "throttle-modal__body"), children: [
|
|
985
|
+
/* @__PURE__ */ jsx8("p", { className: "throttle-muted", children: t.stepUp.description }),
|
|
986
|
+
/* @__PURE__ */ jsx8(
|
|
987
|
+
Field,
|
|
988
|
+
{
|
|
989
|
+
label: t.stepUp.password,
|
|
990
|
+
type: "password",
|
|
991
|
+
autoComplete: "current-password",
|
|
992
|
+
value: password,
|
|
993
|
+
onChange: setPassword,
|
|
994
|
+
required: true,
|
|
995
|
+
elementKey: "stepUp.passwordField"
|
|
996
|
+
}
|
|
997
|
+
),
|
|
998
|
+
/* @__PURE__ */ jsxs4("div", { className: "throttle-actions", children: [
|
|
999
|
+
/* @__PURE__ */ jsx8(Button, { type: "submit", elementKey: "stepUp.submitButton", children: t.stepUp.submitButton }),
|
|
1000
|
+
/* @__PURE__ */ jsx8(
|
|
1001
|
+
Button,
|
|
1002
|
+
{
|
|
1003
|
+
variant: "secondary",
|
|
1004
|
+
elementKey: "stepUp.cancelButton",
|
|
1005
|
+
onClick: () => onStepUpResolve(null),
|
|
1006
|
+
children: t.stepUp.cancelButton
|
|
1007
|
+
}
|
|
1008
|
+
)
|
|
1009
|
+
] })
|
|
1010
|
+
] })
|
|
1011
|
+
] }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1012
|
+
/* @__PURE__ */ jsx8("div", { ...a.el("confirm.header", "throttle-modal__header"), children: /* @__PURE__ */ jsx8("h2", { id: titleId, ...a.el("confirm.title", "throttle-modal__title"), children: request.title }) }),
|
|
1013
|
+
/* @__PURE__ */ jsxs4("div", { ...a.el("confirm.body", "throttle-modal__body"), children: [
|
|
1014
|
+
request.body ? /* @__PURE__ */ jsx8("p", { className: "throttle-muted", children: request.body }) : null,
|
|
1015
|
+
/* @__PURE__ */ jsxs4("div", { className: "throttle-actions", children: [
|
|
1016
|
+
/* @__PURE__ */ jsx8(
|
|
1017
|
+
Button,
|
|
1018
|
+
{
|
|
1019
|
+
elementKey: "confirm.confirmButton",
|
|
1020
|
+
variant: request.tone === "danger" ? "danger" : "primary",
|
|
1021
|
+
onClick: () => answer(true),
|
|
1022
|
+
children: request.confirmLabel ?? t.common.delete
|
|
1023
|
+
}
|
|
1024
|
+
),
|
|
1025
|
+
/* @__PURE__ */ jsx8(Button, { elementKey: "confirm.cancelButton", variant: "secondary", onClick: () => answer(false), children: request.cancelLabel ?? t.common.keepIt })
|
|
1026
|
+
] })
|
|
1027
|
+
] })
|
|
1028
|
+
] }) }) : null
|
|
1029
|
+
] });
|
|
1030
|
+
}
|
|
1031
|
+
function useConfirm() {
|
|
1032
|
+
const ask = useContext4(ConfirmContext);
|
|
1033
|
+
const { t } = useCopy();
|
|
1034
|
+
return useMemo3(
|
|
1035
|
+
() => ask ?? (async (request) => window.confirm(request.title || t.common.confirm)),
|
|
1036
|
+
[ask, t]
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// src/react/components/primitives/Card.tsx
|
|
1041
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1042
|
+
function CardHeader(props) {
|
|
1043
|
+
const a = useAppearance();
|
|
1044
|
+
const headerClass = `throttle-card__header${props.branded ? " throttle-card__header--branded" : ""}`;
|
|
1045
|
+
const titleClass = `throttle-card__title${props.branded ? " throttle-card__title--branded" : ""}`;
|
|
1046
|
+
return /* @__PURE__ */ jsxs5("div", { ...a.el("card.header", headerClass), children: [
|
|
1047
|
+
props.logo ? /* @__PURE__ */ jsx9("div", { ...a.el(props.logoKey ?? "card.logo", "throttle-card__logo"), children: props.logo }) : null,
|
|
1048
|
+
/* @__PURE__ */ jsx9("h2", { ...a.el(props.titleKey ?? "card.title", titleClass), children: props.title }),
|
|
1049
|
+
props.description ? /* @__PURE__ */ jsx9("p", { ...a.el("card.description", "throttle-card__description"), children: props.description }) : null
|
|
1050
|
+
] });
|
|
1051
|
+
}
|
|
1052
|
+
function CardBody(props) {
|
|
1053
|
+
const a = useAppearance();
|
|
1054
|
+
return /* @__PURE__ */ jsx9("div", { ...a.el("card.body", "throttle-card__body"), children: props.children });
|
|
1055
|
+
}
|
|
1056
|
+
function CardFooter(props) {
|
|
1057
|
+
const a = useAppearance();
|
|
1058
|
+
const className = `throttle-card__footer${props.ruled ? " throttle-card__footer--ruled" : ""}`;
|
|
1059
|
+
return /* @__PURE__ */ jsx9("div", { ...a.el("card.footer", className), children: props.children });
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// src/react/components/primitives/FieldRow.tsx
|
|
1063
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1064
|
+
function FieldRow({ children }) {
|
|
1065
|
+
const a = useAppearance();
|
|
1066
|
+
return /* @__PURE__ */ jsx10("div", { ...a.el("fieldRow", "throttle-fieldrow"), children });
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/react/components/primitives/Alert.tsx
|
|
1070
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1071
|
+
function Alert(props) {
|
|
1072
|
+
const a = useAppearance();
|
|
1073
|
+
const Slot = a.slots.Alert;
|
|
1074
|
+
if (Slot) return /* @__PURE__ */ jsx11(Slot, { tone: props.tone, children: props.children });
|
|
1075
|
+
return /* @__PURE__ */ jsx11(
|
|
1076
|
+
"p",
|
|
1077
|
+
{
|
|
1078
|
+
...a.el("alert", `throttle-alert throttle-alert--${props.tone}`),
|
|
1079
|
+
role: props.tone === "error" ? "alert" : "status",
|
|
1080
|
+
children: props.children
|
|
1081
|
+
}
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// src/react/components/primitives/Skeleton.tsx
|
|
1086
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
1087
|
+
function Skeleton(props) {
|
|
1088
|
+
const a = useAppearance();
|
|
1089
|
+
const rows = Array.from({ length: props.count ?? 1 });
|
|
1090
|
+
return /* @__PURE__ */ jsx12("div", { "aria-hidden": "true", className: "throttle-stack", children: rows.map((_, i) => /* @__PURE__ */ jsx12("span", { ...a.el("skeleton", "throttle-skeleton") }, i)) });
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// src/react/components/primitives/Dropdown.tsx
|
|
1094
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
1095
|
+
import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1096
|
+
function Dropdown(props) {
|
|
1097
|
+
const a = useAppearance();
|
|
1098
|
+
const [open, setOpen] = useState3(false);
|
|
1099
|
+
const root = useRef3(null);
|
|
1100
|
+
const trigger = useRef3(null);
|
|
1101
|
+
const menu = useRef3(null);
|
|
1102
|
+
useEffect3(() => {
|
|
1103
|
+
if (!open) return;
|
|
1104
|
+
menu.current?.querySelector('[role="menuitem"]')?.focus();
|
|
1105
|
+
const onDown = (e) => {
|
|
1106
|
+
if (root.current && !root.current.contains(e.target)) setOpen(false);
|
|
1107
|
+
};
|
|
1108
|
+
const onKey = (e) => {
|
|
1109
|
+
if (e.key === "Escape") {
|
|
1110
|
+
setOpen(false);
|
|
1111
|
+
trigger.current?.focus();
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
document.addEventListener("mousedown", onDown);
|
|
1115
|
+
document.addEventListener("keydown", onKey);
|
|
1116
|
+
return () => {
|
|
1117
|
+
document.removeEventListener("mousedown", onDown);
|
|
1118
|
+
document.removeEventListener("keydown", onKey);
|
|
1119
|
+
};
|
|
1120
|
+
}, [open]);
|
|
1121
|
+
const move = (delta) => {
|
|
1122
|
+
const items = Array.from(menu.current?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
1123
|
+
if (items.length === 0) return;
|
|
1124
|
+
const at = items.indexOf(document.activeElement);
|
|
1125
|
+
const next = (at + delta + items.length) % items.length;
|
|
1126
|
+
items[next < 0 ? items.length - 1 : next].focus();
|
|
1127
|
+
};
|
|
1128
|
+
return /* @__PURE__ */ jsxs6("div", { ref: root, style: { position: "relative", display: "inline-block" }, children: [
|
|
1129
|
+
/* @__PURE__ */ jsx13(
|
|
1130
|
+
"button",
|
|
1131
|
+
{
|
|
1132
|
+
ref: trigger,
|
|
1133
|
+
type: "button",
|
|
1134
|
+
...a.el(props.triggerKey ?? "dropdown.trigger", "throttle-dropdown__trigger"),
|
|
1135
|
+
"aria-haspopup": "menu",
|
|
1136
|
+
"aria-expanded": open,
|
|
1137
|
+
"aria-label": props.label,
|
|
1138
|
+
onClick: () => setOpen((v) => !v),
|
|
1139
|
+
children: props.trigger
|
|
1140
|
+
}
|
|
1141
|
+
),
|
|
1142
|
+
open ? /* @__PURE__ */ jsx13(
|
|
1143
|
+
"div",
|
|
1144
|
+
{
|
|
1145
|
+
ref: menu,
|
|
1146
|
+
...a.el(props.menuKey ?? "dropdown.menu", "throttle-dropdown__menu"),
|
|
1147
|
+
role: "menu",
|
|
1148
|
+
onClick: () => setOpen(false),
|
|
1149
|
+
onKeyDown: (e) => {
|
|
1150
|
+
if (e.key === "ArrowDown") {
|
|
1151
|
+
e.preventDefault();
|
|
1152
|
+
move(1);
|
|
1153
|
+
}
|
|
1154
|
+
if (e.key === "ArrowUp") {
|
|
1155
|
+
e.preventDefault();
|
|
1156
|
+
move(-1);
|
|
1157
|
+
}
|
|
1158
|
+
},
|
|
1159
|
+
children: props.children
|
|
1160
|
+
}
|
|
1161
|
+
) : null
|
|
1162
|
+
] });
|
|
1163
|
+
}
|
|
1164
|
+
function DropdownItem(props) {
|
|
1165
|
+
const a = useAppearance();
|
|
1166
|
+
const attrs = a.el(props.itemKey ?? "dropdown.item", "throttle-dropdown__item");
|
|
1167
|
+
if (props.href) {
|
|
1168
|
+
return /* @__PURE__ */ jsx13("a", { ...attrs, role: "menuitem", href: props.href, tabIndex: -1, children: props.children });
|
|
1169
|
+
}
|
|
1170
|
+
return /* @__PURE__ */ jsx13("button", { ...attrs, role: "menuitem", type: "button", tabIndex: -1, onClick: props.onClick, children: props.children });
|
|
1171
|
+
}
|
|
1172
|
+
function DropdownSeparator() {
|
|
1173
|
+
const a = useAppearance();
|
|
1174
|
+
return /* @__PURE__ */ jsx13("hr", { ...a.el("dropdown.separator", "throttle-dropdown__separator") });
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/react/components/primitives/Tabs.tsx
|
|
1178
|
+
import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1179
|
+
function Tabs(props) {
|
|
1180
|
+
const a = useAppearance();
|
|
1181
|
+
const at = props.tabs.findIndex((t) => t.id === props.active);
|
|
1182
|
+
const vertical = props.orientation === "vertical";
|
|
1183
|
+
const listClass = vertical ? "throttle-tabs__rail" : "throttle-tabs__list";
|
|
1184
|
+
const itemClass = vertical ? "throttle-tabs__railitem" : "throttle-tabs__tab";
|
|
1185
|
+
return /* @__PURE__ */ jsx14(
|
|
1186
|
+
"div",
|
|
1187
|
+
{
|
|
1188
|
+
...a.el("tabs.list", listClass),
|
|
1189
|
+
role: "tablist",
|
|
1190
|
+
"aria-orientation": vertical ? "vertical" : void 0,
|
|
1191
|
+
onKeyDown: (e) => {
|
|
1192
|
+
const next = vertical ? "ArrowDown" : "ArrowRight";
|
|
1193
|
+
const prev = vertical ? "ArrowUp" : "ArrowLeft";
|
|
1194
|
+
const delta = e.key === next ? 1 : e.key === prev ? -1 : 0;
|
|
1195
|
+
if (!delta) return;
|
|
1196
|
+
e.preventDefault();
|
|
1197
|
+
const nextIndex = (at + delta + props.tabs.length) % props.tabs.length;
|
|
1198
|
+
props.onChange(props.tabs[nextIndex].id);
|
|
1199
|
+
e.currentTarget.querySelectorAll('[role="tab"]')[nextIndex]?.focus();
|
|
1200
|
+
},
|
|
1201
|
+
children: props.tabs.map((tab) => {
|
|
1202
|
+
const selected = tab.id === props.active;
|
|
1203
|
+
const { className, ...rest } = a.el("tabs.tab", itemClass);
|
|
1204
|
+
return /* @__PURE__ */ jsxs7(
|
|
1205
|
+
"button",
|
|
1206
|
+
{
|
|
1207
|
+
...rest,
|
|
1208
|
+
className: [className, selected ? `${itemClass}--active` : null].filter(Boolean).join(" "),
|
|
1209
|
+
role: "tab",
|
|
1210
|
+
type: "button",
|
|
1211
|
+
id: props.idBase ? `${props.idBase}-tab-${tab.id}` : void 0,
|
|
1212
|
+
"aria-controls": props.idBase ? `${props.idBase}-panel-${tab.id}` : void 0,
|
|
1213
|
+
"aria-selected": selected,
|
|
1214
|
+
tabIndex: selected ? 0 : -1,
|
|
1215
|
+
onClick: () => props.onChange(tab.id),
|
|
1216
|
+
children: [
|
|
1217
|
+
tab.icon,
|
|
1218
|
+
tab.label
|
|
1219
|
+
]
|
|
1220
|
+
},
|
|
1221
|
+
tab.id
|
|
1222
|
+
);
|
|
1223
|
+
})
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// src/react/components/primitives/DataTable.tsx
|
|
1229
|
+
import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1230
|
+
function DataTable(props) {
|
|
1231
|
+
const a = useAppearance();
|
|
1232
|
+
const clickable = Boolean(props.onRowClick);
|
|
1233
|
+
return (
|
|
1234
|
+
// Tables are the one thing allowed to be wider than the panel; it scrolls
|
|
1235
|
+
// rather than forcing the whole page to.
|
|
1236
|
+
/* @__PURE__ */ jsx15("div", { className: "throttle-table__scroll", children: /* @__PURE__ */ jsxs8("table", { ...a.el(props.tableKey ?? "table", "throttle-table"), children: [
|
|
1237
|
+
/* @__PURE__ */ jsx15("thead", { children: /* @__PURE__ */ jsx15("tr", { children: props.columns.map((col) => /* @__PURE__ */ jsx15("th", { ...a.el("table.head", "throttle-table__head"), scope: "col", children: col.label }, col.key)) }) }),
|
|
1238
|
+
/* @__PURE__ */ jsx15("tbody", { children: props.rows.map((row) => /* @__PURE__ */ jsx15(
|
|
1239
|
+
"tr",
|
|
1240
|
+
{
|
|
1241
|
+
...a.el(props.rowElementKey ?? "table.row", "throttle-table__row"),
|
|
1242
|
+
tabIndex: clickable ? 0 : void 0,
|
|
1243
|
+
onClick: clickable ? () => props.onRowClick(row) : void 0,
|
|
1244
|
+
onKeyDown: clickable ? (e) => {
|
|
1245
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1246
|
+
e.preventDefault();
|
|
1247
|
+
props.onRowClick(row);
|
|
1248
|
+
}
|
|
1249
|
+
} : void 0,
|
|
1250
|
+
children: props.columns.map((col) => /* @__PURE__ */ jsx15("td", { ...a.el("table.cell", "throttle-table__cell"), children: col.render ? col.render(row) : String(row[col.key] ?? "") }, col.key))
|
|
1251
|
+
},
|
|
1252
|
+
props.rowKey(row)
|
|
1253
|
+
)) })
|
|
1254
|
+
] }) })
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// src/react/components/primitives/EmptyState.tsx
|
|
1259
|
+
import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1260
|
+
function EmptyState(props) {
|
|
1261
|
+
const a = useAppearance();
|
|
1262
|
+
const Slot = a.slots.EmptyState;
|
|
1263
|
+
if (Slot) return /* @__PURE__ */ jsx16(Slot, { title: props.title, description: props.description });
|
|
1264
|
+
return /* @__PURE__ */ jsxs9("div", { ...a.el("emptyState", "throttle-empty"), children: [
|
|
1265
|
+
/* @__PURE__ */ jsx16("p", { ...a.el("emptyState.title", "throttle-empty__title"), children: props.title }),
|
|
1266
|
+
props.description ? /* @__PURE__ */ jsx16("p", { ...a.el("emptyState.description", "throttle-empty__description"), children: props.description }) : null
|
|
1267
|
+
] });
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// src/react/components/primitives/Avatar.tsx
|
|
1271
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1272
|
+
function Avatar(props) {
|
|
1273
|
+
const a = useAppearance();
|
|
1274
|
+
const Slot = a.slots.Avatar;
|
|
1275
|
+
if (Slot) return /* @__PURE__ */ jsx17(Slot, { name: props.name, email: props.email });
|
|
1276
|
+
const initials = props.name.split(" ").map((part) => part.trim()[0]).filter(Boolean).slice(0, 2).join("") || props.email.trim()[0] || "?";
|
|
1277
|
+
return /* @__PURE__ */ jsx17("span", { ...a.el("avatar", "throttle-avatar"), title: props.email, children: initials.toUpperCase() });
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/react/components/primitives/Badge.tsx
|
|
1281
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1282
|
+
function Badge(props) {
|
|
1283
|
+
const a = useAppearance();
|
|
1284
|
+
const tone = props.tone ?? "neutral";
|
|
1285
|
+
const base = tone === "neutral" ? "throttle-badge" : `throttle-badge throttle-badge--${tone}`;
|
|
1286
|
+
return /* @__PURE__ */ jsx18("span", { ...a.el(props.elementKey ?? "badge", base), children: props.children });
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
export {
|
|
1290
|
+
THROTTLE_ELEMENT_KEYS,
|
|
1291
|
+
AppearanceProvider,
|
|
1292
|
+
useAppearance,
|
|
1293
|
+
useAppearanceProviderValue,
|
|
1294
|
+
enUS,
|
|
1295
|
+
LocalizationProvider,
|
|
1296
|
+
useCopy,
|
|
1297
|
+
CardHeader,
|
|
1298
|
+
CardBody,
|
|
1299
|
+
CardFooter,
|
|
1300
|
+
Spinner,
|
|
1301
|
+
Button,
|
|
1302
|
+
Field,
|
|
1303
|
+
FieldGroup,
|
|
1304
|
+
FieldRow,
|
|
1305
|
+
Alert,
|
|
1306
|
+
Skeleton,
|
|
1307
|
+
useCardClass,
|
|
1308
|
+
Modal,
|
|
1309
|
+
ConfirmProvider,
|
|
1310
|
+
useConfirm,
|
|
1311
|
+
Dropdown,
|
|
1312
|
+
DropdownItem,
|
|
1313
|
+
DropdownSeparator,
|
|
1314
|
+
Tabs,
|
|
1315
|
+
DataTable,
|
|
1316
|
+
EmptyState,
|
|
1317
|
+
Avatar,
|
|
1318
|
+
Badge
|
|
1319
|
+
};
|
|
1320
|
+
//# sourceMappingURL=chunk-CUNMJ2PH.js.map
|