@usethrottle/auth 0.1.2 → 0.2.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/CLAUDE.md +25 -0
- package/README.md +258 -0
- package/dist/chunk-AIXBUDOT.js +50 -0
- package/dist/chunk-AIXBUDOT.js.map +1 -0
- package/dist/chunk-HGBAIM6B.js +140 -0
- package/dist/chunk-HGBAIM6B.js.map +1 -0
- package/dist/{chunk-SOPTBXF3.js → chunk-SZ427BQ3.js} +48 -3
- package/dist/chunk-SZ427BQ3.js.map +1 -0
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +377 -8
- package/dist/index.js.map +1 -1
- package/dist/react/components/index.cjs +4558 -0
- package/dist/react/components/index.cjs.map +1 -0
- package/dist/react/components/index.d.cts +735 -0
- package/dist/react/components/index.d.ts +735 -0
- package/dist/react/components/index.js +3918 -0
- package/dist/react/components/index.js.map +1 -0
- package/dist/react/forms/index.cjs +3 -0
- package/dist/react/forms/index.cjs.map +1 -1
- package/dist/react/forms/index.js +5 -46
- package/dist/react/forms/index.js.map +1 -1
- package/dist/react/index.cjs +16 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +278 -3
- package/dist/react/index.d.ts +278 -3
- package/dist/react/index.js +17 -122
- package/dist/react/index.js.map +1 -1
- package/dist/styles.css +422 -0
- package/package.json +49 -11
- package/dist/chunk-5GLLPF6L.js +0 -49
- package/dist/chunk-5GLLPF6L.js.map +0 -1
- package/dist/chunk-SOPTBXF3.js.map +0 -1
- package/dist/chunk-ZMNCURDE.js +0 -1
- package/dist/chunk-ZMNCURDE.js.map +0 -1
package/dist/react/index.js
CHANGED
|
@@ -1,131 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
useAddresses,
|
|
4
|
+
useCustomer,
|
|
5
|
+
useInvoice,
|
|
6
|
+
useInvoices,
|
|
7
|
+
useOrder,
|
|
8
|
+
useOrders,
|
|
9
|
+
useSavedCards,
|
|
10
|
+
useSubscriptions
|
|
11
|
+
} from "../chunk-HGBAIM6B.js";
|
|
7
12
|
import {
|
|
8
13
|
ThrottleAuthError,
|
|
14
|
+
ThrottleAuthProvider,
|
|
9
15
|
createAccount,
|
|
10
16
|
createThrottleAuth,
|
|
11
17
|
localStorageAdapter,
|
|
12
|
-
memoryStorage
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
17
|
-
function asError(err) {
|
|
18
|
-
if (err instanceof ThrottleAuthError) return err;
|
|
19
|
-
const anyErr = err;
|
|
20
|
-
return new ThrottleAuthError({
|
|
21
|
-
code: anyErr?.code ?? "request_failed",
|
|
22
|
-
message: anyErr?.message ?? String(err),
|
|
23
|
-
statusCode: anyErr?.statusCode ?? 0
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
function useResource(empty, load, deps) {
|
|
27
|
-
const { status, emailVerified } = useAuth();
|
|
28
|
-
const [data, setData] = useState(empty);
|
|
29
|
-
const [isLoading, setIsLoading] = useState(status === "loading");
|
|
30
|
-
const [error, setError] = useState(null);
|
|
31
|
-
const gen = useRef(0);
|
|
32
|
-
const alive = useRef(true);
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
alive.current = true;
|
|
35
|
-
return () => {
|
|
36
|
-
alive.current = false;
|
|
37
|
-
gen.current++;
|
|
38
|
-
};
|
|
39
|
-
}, []);
|
|
40
|
-
const refetch = useCallback(async () => {
|
|
41
|
-
const mine = ++gen.current;
|
|
42
|
-
setIsLoading(true);
|
|
43
|
-
try {
|
|
44
|
-
const next = await load();
|
|
45
|
-
if (!alive.current || gen.current !== mine) return;
|
|
46
|
-
setData(next);
|
|
47
|
-
setError(null);
|
|
48
|
-
} catch (err) {
|
|
49
|
-
if (!alive.current || gen.current !== mine) return;
|
|
50
|
-
setError(asError(err));
|
|
51
|
-
} finally {
|
|
52
|
-
if (alive.current && gen.current === mine) setIsLoading(false);
|
|
53
|
-
}
|
|
54
|
-
}, deps);
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
if (status !== "authenticated") {
|
|
57
|
-
gen.current++;
|
|
58
|
-
setData(empty);
|
|
59
|
-
setError(null);
|
|
60
|
-
setIsLoading(status === "loading");
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
void refetch();
|
|
64
|
-
}, [status, emailVerified, refetch]);
|
|
65
|
-
return { data, isLoading, error, refetch };
|
|
66
|
-
}
|
|
67
|
-
function useCustomer() {
|
|
68
|
-
const { account } = useAuth();
|
|
69
|
-
const res = useResource(null, () => account.getProfile(), [account]);
|
|
70
|
-
const update = useCallback(async (patch) => {
|
|
71
|
-
await account.updateProfile(patch);
|
|
72
|
-
await res.refetch();
|
|
73
|
-
}, [account, res]);
|
|
74
|
-
return { ...res, update };
|
|
75
|
-
}
|
|
76
|
-
function useAddresses() {
|
|
77
|
-
const { account } = useAuth();
|
|
78
|
-
const res = useResource([], () => account.listAddresses(), [account]);
|
|
79
|
-
const after = async (p) => {
|
|
80
|
-
await p;
|
|
81
|
-
await res.refetch();
|
|
82
|
-
};
|
|
83
|
-
return {
|
|
84
|
-
...res,
|
|
85
|
-
create: (input) => after(account.createAddress(input)),
|
|
86
|
-
update: (id, patch) => after(account.updateAddress(id, patch)),
|
|
87
|
-
remove: (id) => after(account.deleteAddress(id))
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
function useSavedCards() {
|
|
91
|
-
const { account } = useAuth();
|
|
92
|
-
const res = useResource([], () => account.listPaymentMethods(), [account]);
|
|
93
|
-
const after = async (p) => {
|
|
94
|
-
await p;
|
|
95
|
-
await res.refetch();
|
|
96
|
-
};
|
|
97
|
-
return {
|
|
98
|
-
...res,
|
|
99
|
-
setDefault: (id) => after(account.setDefaultPaymentMethod(id)),
|
|
100
|
-
remove: (id) => after(account.deletePaymentMethod(id))
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
function useOrders(page) {
|
|
104
|
-
const { account } = useAuth();
|
|
105
|
-
return useResource([], () => account.listOrders(page), [account, page?.cursor, page?.limit]);
|
|
106
|
-
}
|
|
107
|
-
function useInvoices(page) {
|
|
108
|
-
const { account } = useAuth();
|
|
109
|
-
return useResource([], () => account.listInvoices(page), [account, page?.cursor, page?.limit]);
|
|
110
|
-
}
|
|
111
|
-
function useSubscriptions(page) {
|
|
112
|
-
const { account } = useAuth();
|
|
113
|
-
const res = useResource(
|
|
114
|
-
[],
|
|
115
|
-
() => account.listSubscriptions(page),
|
|
116
|
-
[account, page?.cursor, page?.limit]
|
|
117
|
-
);
|
|
118
|
-
const after = async (p) => {
|
|
119
|
-
await p;
|
|
120
|
-
await res.refetch();
|
|
121
|
-
};
|
|
122
|
-
return {
|
|
123
|
-
...res,
|
|
124
|
-
cancel: (id, opts) => after(account.cancelSubscription(id, opts)),
|
|
125
|
-
pause: (id) => after(account.pauseSubscription(id)),
|
|
126
|
-
resume: (id) => after(account.resumeSubscription(id))
|
|
127
|
-
};
|
|
128
|
-
}
|
|
18
|
+
memoryStorage,
|
|
19
|
+
useAuth,
|
|
20
|
+
useAuthContext
|
|
21
|
+
} from "../chunk-SZ427BQ3.js";
|
|
129
22
|
export {
|
|
130
23
|
ThrottleAuthError,
|
|
131
24
|
ThrottleAuthProvider,
|
|
@@ -137,7 +30,9 @@ export {
|
|
|
137
30
|
useAuth,
|
|
138
31
|
useAuthContext,
|
|
139
32
|
useCustomer,
|
|
33
|
+
useInvoice,
|
|
140
34
|
useInvoices,
|
|
35
|
+
useOrder,
|
|
141
36
|
useOrders,
|
|
142
37
|
useSavedCards,
|
|
143
38
|
useSubscriptions
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
|
|
2
|
+
.throttle-root {
|
|
3
|
+
--throttle-color-primary: #18181b;
|
|
4
|
+
--throttle-color-primary-foreground: #ffffff;
|
|
5
|
+
--throttle-color-background: #ffffff;
|
|
6
|
+
--throttle-color-surface: #f8fafc;
|
|
7
|
+
--throttle-color-foreground: #0e1116;
|
|
8
|
+
--throttle-color-muted: #eceef2;
|
|
9
|
+
--throttle-color-muted-foreground: #5b6472;
|
|
10
|
+
--throttle-color-border: #e2e8f0;
|
|
11
|
+
--throttle-color-danger: #c2321f;
|
|
12
|
+
--throttle-color-danger-surface: #fef2f2;
|
|
13
|
+
--throttle-color-danger-border: #fecaca;
|
|
14
|
+
--throttle-color-success: #127a4a;
|
|
15
|
+
--throttle-color-success-surface: #ecfdf5;
|
|
16
|
+
--throttle-color-success-border: #a7f3d0;
|
|
17
|
+
--throttle-color-warning: #92400e;
|
|
18
|
+
--throttle-color-warning-surface: #fffbeb;
|
|
19
|
+
--throttle-color-warning-border: #fde68a;
|
|
20
|
+
--throttle-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
21
|
+
--throttle-font-size: 16px;
|
|
22
|
+
--throttle-font-size-xs: 12px;
|
|
23
|
+
--throttle-font-size-sm: 14px;
|
|
24
|
+
--throttle-font-size-lg: 20px;
|
|
25
|
+
--throttle-font-size-xl: 24px;
|
|
26
|
+
--throttle-line-height: 1.5;
|
|
27
|
+
--throttle-line-height-tight: 1.25;
|
|
28
|
+
--throttle-radius: 12px;
|
|
29
|
+
--throttle-radius-sm: 8px;
|
|
30
|
+
--throttle-radius-full: 999px;
|
|
31
|
+
--throttle-border-width: 1px;
|
|
32
|
+
--throttle-spacing: 8px;
|
|
33
|
+
--throttle-space-1: 4px;
|
|
34
|
+
--throttle-space-2: var(--throttle-spacing);
|
|
35
|
+
--throttle-space-3: 12px;
|
|
36
|
+
--throttle-space-4: 16px;
|
|
37
|
+
--throttle-space-5: 24px;
|
|
38
|
+
--throttle-space-6: 32px;
|
|
39
|
+
--throttle-control-height: 48px;
|
|
40
|
+
--throttle-control-padding-x: 16px;
|
|
41
|
+
--throttle-shadow-card: 0 1px 3px rgba(15,23,42,.08), 0 1px 2px rgba(15,23,42,.04);
|
|
42
|
+
--throttle-shadow-popover: 0 10px 24px rgba(15,23,42,.12), 0 2px 6px rgba(15,23,42,.06);
|
|
43
|
+
--throttle-transition: 150ms cubic-bezier(.2,0,.2,1);
|
|
44
|
+
--throttle-z-modal: 2147483000;
|
|
45
|
+
--throttle-z-popover: 2147482000;
|
|
46
|
+
font-family: var(--throttle-font-family);
|
|
47
|
+
font-size: var(--throttle-font-size);
|
|
48
|
+
line-height: var(--throttle-line-height);
|
|
49
|
+
color: var(--throttle-color-foreground);
|
|
50
|
+
box-sizing: border-box;
|
|
51
|
+
}
|
|
52
|
+
.throttle-root *, .throttle-root *::before, .throttle-root *::after { box-sizing: inherit; }
|
|
53
|
+
|
|
54
|
+
.throttle-root[data-throttle-theme="dark"] {
|
|
55
|
+
--throttle-color-primary: #e6edf3;
|
|
56
|
+
--throttle-color-primary-foreground: #0d1117;
|
|
57
|
+
--throttle-color-background: #0d1117;
|
|
58
|
+
--throttle-color-surface: #1c2129;
|
|
59
|
+
--throttle-color-foreground: #e6edf3;
|
|
60
|
+
--throttle-color-muted: #21262d;
|
|
61
|
+
--throttle-color-muted-foreground: #9aa4b2;
|
|
62
|
+
--throttle-color-border: #30363d;
|
|
63
|
+
--throttle-color-danger: #ff6b58;
|
|
64
|
+
--throttle-color-danger-surface: #2a1513;
|
|
65
|
+
--throttle-color-danger-border: #5c2b25;
|
|
66
|
+
--throttle-color-success: #45c08a;
|
|
67
|
+
--throttle-color-success-surface: #0f2419;
|
|
68
|
+
--throttle-color-success-border: #1f4d36;
|
|
69
|
+
--throttle-color-warning: #e0a53a;
|
|
70
|
+
--throttle-color-warning-surface: #2a2113;
|
|
71
|
+
--throttle-color-warning-border: #5c4a25;
|
|
72
|
+
--throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);
|
|
73
|
+
--throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);
|
|
74
|
+
}
|
|
75
|
+
@media (prefers-color-scheme: dark) {
|
|
76
|
+
.throttle-root[data-throttle-theme="auto"] {
|
|
77
|
+
--throttle-color-primary: #e6edf3;
|
|
78
|
+
--throttle-color-primary-foreground: #0d1117;
|
|
79
|
+
--throttle-color-background: #0d1117;
|
|
80
|
+
--throttle-color-surface: #1c2129;
|
|
81
|
+
--throttle-color-foreground: #e6edf3;
|
|
82
|
+
--throttle-color-muted: #21262d;
|
|
83
|
+
--throttle-color-muted-foreground: #9aa4b2;
|
|
84
|
+
--throttle-color-border: #30363d;
|
|
85
|
+
--throttle-color-danger: #ff6b58;
|
|
86
|
+
--throttle-color-danger-surface: #2a1513;
|
|
87
|
+
--throttle-color-danger-border: #5c2b25;
|
|
88
|
+
--throttle-color-success: #45c08a;
|
|
89
|
+
--throttle-color-success-surface: #0f2419;
|
|
90
|
+
--throttle-color-success-border: #1f4d36;
|
|
91
|
+
--throttle-color-warning: #e0a53a;
|
|
92
|
+
--throttle-color-warning-surface: #2a2113;
|
|
93
|
+
--throttle-color-warning-border: #5c4a25;
|
|
94
|
+
--throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);
|
|
95
|
+
--throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.throttle-card {
|
|
100
|
+
background: var(--throttle-color-background);
|
|
101
|
+
border: var(--throttle-border-width) solid var(--throttle-color-border);
|
|
102
|
+
border-radius: var(--throttle-radius);
|
|
103
|
+
box-shadow: var(--throttle-shadow-card);
|
|
104
|
+
padding: var(--throttle-space-5);
|
|
105
|
+
width: 100%;
|
|
106
|
+
max-width: 400px;
|
|
107
|
+
margin-inline: auto;
|
|
108
|
+
}
|
|
109
|
+
/* Inside a Modal the dialog already provides the surface — background, radius
|
|
110
|
+
and elevation. A card that draws its own on top is the "box inside a box":
|
|
111
|
+
two shadowed rounded rectangles nested, with the card's max-width stranding
|
|
112
|
+
it in the middle of a much wider dialog. */
|
|
113
|
+
.throttle-card--bare { border: none; box-shadow: none; background: none; max-width: none; }
|
|
114
|
+
.throttle-card__header { margin-bottom: var(--throttle-space-4); }
|
|
115
|
+
.throttle-card__header--branded { text-align: center; }
|
|
116
|
+
.throttle-card__logo { display: flex; justify-content: center; margin-bottom: var(--throttle-space-3); }
|
|
117
|
+
.throttle-card__title { font-size: var(--throttle-font-size-xl); line-height: var(--throttle-line-height-tight); font-weight: 600; letter-spacing: -0.02em; margin: 0; }
|
|
118
|
+
.throttle-card__title--branded { font-size: var(--throttle-font-size-xl); }
|
|
119
|
+
.throttle-card__description { color: var(--throttle-color-muted-foreground); margin: var(--throttle-space-1) 0 0; }
|
|
120
|
+
.throttle-card__body { display: flex; flex-direction: column; gap: var(--throttle-space-3); }
|
|
121
|
+
.throttle-card__footer { margin-top: var(--throttle-space-4); text-align: center; }
|
|
122
|
+
.throttle-card__footer--ruled { border-top: var(--throttle-border-width) solid var(--throttle-color-border); padding-top: var(--throttle-space-4); }
|
|
123
|
+
|
|
124
|
+
.throttle-field { display: flex; flex-direction: column; gap: var(--throttle-space-1); }
|
|
125
|
+
.throttle-field__labelrow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--throttle-space-2); }
|
|
126
|
+
.throttle-field__label { font-weight: 500; }
|
|
127
|
+
.throttle-field__input {
|
|
128
|
+
font: inherit;
|
|
129
|
+
font-size: var(--throttle-font-size);
|
|
130
|
+
color: var(--throttle-color-foreground);
|
|
131
|
+
background: var(--throttle-color-background);
|
|
132
|
+
border: var(--throttle-border-width) solid var(--throttle-color-border);
|
|
133
|
+
border-radius: var(--throttle-radius-sm);
|
|
134
|
+
/* padding-block stays 0 so min-height alone owns the height, exactly as
|
|
135
|
+
.throttle-button does. With 12px vertical padding the box computed to
|
|
136
|
+
50px against the button's 48px — a 2px mismatch, measured in a browser
|
|
137
|
+
across 19 controls. An input element centres its own text vertically. */
|
|
138
|
+
padding-block: 0;
|
|
139
|
+
padding-inline: var(--throttle-control-padding-x);
|
|
140
|
+
min-height: var(--throttle-control-height);
|
|
141
|
+
width: 100%;
|
|
142
|
+
transition: border-color var(--throttle-transition), box-shadow var(--throttle-transition);
|
|
143
|
+
}
|
|
144
|
+
.throttle-field__input:focus-visible {
|
|
145
|
+
outline: none;
|
|
146
|
+
border-color: var(--throttle-color-primary);
|
|
147
|
+
box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary);
|
|
148
|
+
}
|
|
149
|
+
.throttle-field__input:disabled { background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground); cursor: not-allowed; }
|
|
150
|
+
.throttle-field__input::placeholder { color: var(--throttle-color-muted-foreground); opacity: 1; }
|
|
151
|
+
.throttle-field__input--grouped { border: none; border-radius: 0; padding-inline: 0; }
|
|
152
|
+
.throttle-field__hint { color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-xs); }
|
|
153
|
+
/* Always mounted, so it reserves its line whether or not there is an error:
|
|
154
|
+
an error appearing on submit must not push the submit button out from
|
|
155
|
+
under the buyer's pointer. */
|
|
156
|
+
.throttle-field__error { color: var(--throttle-color-danger); font-size: var(--throttle-font-size-xs); min-height: 1rem; line-height: 1rem; }
|
|
157
|
+
|
|
158
|
+
.throttle-field-group {
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
border: var(--throttle-border-width) solid var(--throttle-color-border);
|
|
162
|
+
border-radius: var(--throttle-radius-sm);
|
|
163
|
+
overflow: hidden;
|
|
164
|
+
}
|
|
165
|
+
.throttle-field-group__item { padding: var(--throttle-space-3) var(--throttle-control-padding-x); }
|
|
166
|
+
.throttle-field-group__divider { height: var(--throttle-border-width); background: var(--throttle-color-border); }
|
|
167
|
+
.throttle-field-group__forgot { margin-left: auto; }
|
|
168
|
+
|
|
169
|
+
.throttle-button {
|
|
170
|
+
font: inherit;
|
|
171
|
+
font-weight: 500;
|
|
172
|
+
border-radius: var(--throttle-radius-sm);
|
|
173
|
+
border: var(--throttle-border-width) solid transparent;
|
|
174
|
+
padding-inline: var(--throttle-control-padding-x);
|
|
175
|
+
padding-block: 0;
|
|
176
|
+
min-height: var(--throttle-control-height);
|
|
177
|
+
display: inline-flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
justify-content: center;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
transition: opacity var(--throttle-transition), background-color var(--throttle-transition), border-color var(--throttle-transition), filter var(--throttle-transition), box-shadow var(--throttle-transition);
|
|
182
|
+
}
|
|
183
|
+
.throttle-button:disabled { opacity: .55; cursor: not-allowed; }
|
|
184
|
+
.throttle-button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary); }
|
|
185
|
+
.throttle-button--primary { background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground); width: 100%; }
|
|
186
|
+
.throttle-button--primary:hover:not(:disabled) { filter: brightness(0.92); }
|
|
187
|
+
.throttle-button--secondary { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); border-color: var(--throttle-color-border); }
|
|
188
|
+
.throttle-button--secondary:hover:not(:disabled) { background: var(--throttle-color-muted); }
|
|
189
|
+
.throttle-button--danger { background: transparent; color: var(--throttle-color-danger); border-color: var(--throttle-color-border); }
|
|
190
|
+
.throttle-button--danger:hover:not(:disabled) { background: color-mix(in srgb, var(--throttle-color-danger) 10%, transparent); }
|
|
191
|
+
/* Link-style actions are the rows panel default, and they carry Delete,
|
|
192
|
+
Remove, Cancel and Sign out. Zero padding with min-height 0 made those
|
|
193
|
+
roughly 20px tall and ~8px apart — a mis-tap on a phone lands on something
|
|
194
|
+
irreversible. The target is expanded with vertical padding and pulled back
|
|
195
|
+
with an equal negative margin, so the hit box clears 44px while the text
|
|
196
|
+
sits exactly where it did. Collapsing this back to zero padding shrinks the
|
|
197
|
+
target again. */
|
|
198
|
+
.throttle-button--link { background: none; border: none; color: var(--throttle-color-primary); padding-inline: 0; padding-block: var(--throttle-space-3); margin-block: calc(var(--throttle-space-3) * -1); min-height: 0; text-decoration: underline; width: auto; }
|
|
199
|
+
.throttle-button--link:hover:not(:disabled) { opacity: .8; }
|
|
200
|
+
.throttle-button--linkDanger { background: none; border: none; color: var(--throttle-color-danger); padding-inline: 0; padding-block: var(--throttle-space-3); margin-block: calc(var(--throttle-space-3) * -1); min-height: 0; text-decoration: underline; width: auto; }
|
|
201
|
+
.throttle-button--linkDanger:hover:not(:disabled) { opacity: .8; }
|
|
202
|
+
.throttle-button--xs { font-size: var(--throttle-font-size-xs); }
|
|
203
|
+
|
|
204
|
+
.throttle-alert { border-radius: var(--throttle-radius-sm); padding: var(--throttle-space-3); font-size: var(--throttle-font-size-sm); }
|
|
205
|
+
.throttle-alert--error { background: var(--throttle-color-danger-surface); border: var(--throttle-border-width) solid var(--throttle-color-danger-border); color: var(--throttle-color-danger); }
|
|
206
|
+
.throttle-alert--success { background: var(--throttle-color-success-surface); border: var(--throttle-border-width) solid var(--throttle-color-success-border); color: var(--throttle-color-success); }
|
|
207
|
+
.throttle-alert--info { background: var(--throttle-color-surface); color: var(--throttle-color-muted-foreground); }
|
|
208
|
+
|
|
209
|
+
.throttle-spinner {
|
|
210
|
+
display: inline-block; width: 1em; height: 1em;
|
|
211
|
+
border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
|
|
212
|
+
animation: throttle-spin 700ms linear infinite;
|
|
213
|
+
}
|
|
214
|
+
@keyframes throttle-spin { to { transform: rotate(360deg); } }
|
|
215
|
+
@media (prefers-reduced-motion: reduce) { .throttle-spinner { animation-duration: 2.4s; } }
|
|
216
|
+
|
|
217
|
+
.throttle-skeleton {
|
|
218
|
+
background: var(--throttle-color-muted);
|
|
219
|
+
border-radius: var(--throttle-radius-sm);
|
|
220
|
+
animation: throttle-pulse 1.4s ease-in-out infinite; height: 2.5em; }
|
|
221
|
+
@keyframes throttle-pulse { 50% { opacity: .5; } }
|
|
222
|
+
@media (prefers-reduced-motion: reduce) { .throttle-skeleton { animation: none; } }
|
|
223
|
+
|
|
224
|
+
.throttle-modal__overlay {
|
|
225
|
+
position: fixed; inset: 0; z-index: var(--throttle-z-modal);
|
|
226
|
+
background: rgba(14,17,22,.45);
|
|
227
|
+
display: flex; align-items: center; justify-content: center;
|
|
228
|
+
padding: var(--throttle-space-4);
|
|
229
|
+
overflow-y: auto;
|
|
230
|
+
}
|
|
231
|
+
.throttle-modal__container {
|
|
232
|
+
background: var(--throttle-color-background);
|
|
233
|
+
border-radius: var(--throttle-radius);
|
|
234
|
+
box-shadow: var(--throttle-shadow-popover);
|
|
235
|
+
max-width: 720px; width: 100%; max-height: 90vh; overflow-y: auto;
|
|
236
|
+
position: relative;
|
|
237
|
+
}
|
|
238
|
+
/* After the base rule on purpose: both are single-class, so equal specificity
|
|
239
|
+
means source order decides. Declared before it, this silently lost. */
|
|
240
|
+
.throttle-modal__container--compact { max-width: 440px; }
|
|
241
|
+
.throttle-modal__close {
|
|
242
|
+
position: absolute; top: var(--throttle-space-3); right: var(--throttle-space-3);
|
|
243
|
+
background: none; border: none; cursor: pointer; font-size: var(--throttle-font-size-lg);
|
|
244
|
+
color: var(--throttle-color-muted-foreground);
|
|
245
|
+
transition: color var(--throttle-transition);
|
|
246
|
+
}
|
|
247
|
+
.throttle-modal__close:hover { color: var(--throttle-color-foreground); }
|
|
248
|
+
.throttle-modal__header { padding: var(--throttle-space-4) var(--throttle-space-4) 0; }
|
|
249
|
+
.throttle-modal__title { font-size: var(--throttle-font-size-lg); line-height: var(--throttle-line-height-tight); font-weight: 600; margin: 0; }
|
|
250
|
+
.throttle-modal__body { padding: var(--throttle-space-4); }
|
|
251
|
+
|
|
252
|
+
.throttle-dropdown__trigger { background: none; border: none; cursor: pointer; padding: 0; border-radius: var(--throttle-radius-full); transition: background-color var(--throttle-transition); }
|
|
253
|
+
.throttle-dropdown__trigger:hover { background: var(--throttle-color-surface); }
|
|
254
|
+
.throttle-dropdown__trigger:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: 2px; }
|
|
255
|
+
.throttle-dropdown__menu {
|
|
256
|
+
position: absolute; right: 0; top: calc(100% + var(--throttle-space-2));
|
|
257
|
+
z-index: var(--throttle-z-popover);
|
|
258
|
+
min-width: 260px;
|
|
259
|
+
background: var(--throttle-color-background);
|
|
260
|
+
border: var(--throttle-border-width) solid var(--throttle-color-border);
|
|
261
|
+
border-radius: var(--throttle-radius);
|
|
262
|
+
box-shadow: var(--throttle-shadow-popover);
|
|
263
|
+
padding: var(--throttle-space-1);
|
|
264
|
+
}
|
|
265
|
+
.throttle-dropdown__item {
|
|
266
|
+
display: flex; align-items: center; gap: var(--throttle-space-2);
|
|
267
|
+
width: 100%; text-align: left; font: inherit; color: inherit;
|
|
268
|
+
background: none; border: none; cursor: pointer;
|
|
269
|
+
padding: var(--throttle-space-3);
|
|
270
|
+
border-radius: var(--throttle-radius-sm);
|
|
271
|
+
text-decoration: none;
|
|
272
|
+
transition: background-color var(--throttle-transition);
|
|
273
|
+
}
|
|
274
|
+
.throttle-dropdown__item:hover { background: var(--throttle-color-surface); }
|
|
275
|
+
.throttle-dropdown__item:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
|
|
276
|
+
.throttle-dropdown__separator { height: 1px; background: var(--throttle-color-border); margin: var(--throttle-space-1) 0; border: 0; }
|
|
277
|
+
|
|
278
|
+
.throttle-tabs__list {
|
|
279
|
+
display: flex; gap: var(--throttle-space-1);
|
|
280
|
+
border-bottom: var(--throttle-border-width) solid var(--throttle-color-border);
|
|
281
|
+
overflow-x: auto; scrollbar-width: thin;
|
|
282
|
+
}
|
|
283
|
+
.throttle-tabs__tab {
|
|
284
|
+
font: inherit; background: none; border: none; cursor: pointer; white-space: nowrap;
|
|
285
|
+
padding: var(--throttle-space-3);
|
|
286
|
+
color: var(--throttle-color-muted-foreground);
|
|
287
|
+
border-bottom: 2px solid transparent;
|
|
288
|
+
transition: color var(--throttle-transition), border-color var(--throttle-transition);
|
|
289
|
+
}
|
|
290
|
+
.throttle-tabs__tab:hover { color: var(--throttle-color-foreground); }
|
|
291
|
+
.throttle-tabs__tab--active { color: var(--throttle-color-foreground); border-bottom-color: var(--throttle-color-primary); }
|
|
292
|
+
.throttle-tabs__panel { padding-top: var(--throttle-space-4); }
|
|
293
|
+
|
|
294
|
+
/* Paints its own ground. A UserButton in a navbar should stay transparent, but
|
|
295
|
+
the dashboard is a large surface of themed text: in dark theme its foreground
|
|
296
|
+
is near-white, so on a light host page it was rendering white-on-white. */
|
|
297
|
+
.throttle-account-root { container-type: inline-size; background: var(--throttle-color-background); color: var(--throttle-color-foreground); }
|
|
298
|
+
.throttle-account { display: flex; gap: var(--throttle-space-5); align-items: flex-start; }
|
|
299
|
+
.throttle-account__content { flex: 1; min-width: 0; }
|
|
300
|
+
.throttle-tabs__rail { flex: none; width: 220px; display: flex; flex-direction: column; gap: 2px; }
|
|
301
|
+
.throttle-tabs__railitem {
|
|
302
|
+
font: inherit;
|
|
303
|
+
font-size: var(--throttle-font-size-sm);
|
|
304
|
+
display: flex;
|
|
305
|
+
align-items: center;
|
|
306
|
+
gap: var(--throttle-space-3);
|
|
307
|
+
width: 100%;
|
|
308
|
+
text-align: left;
|
|
309
|
+
background: none;
|
|
310
|
+
border: none;
|
|
311
|
+
border-radius: var(--throttle-radius-sm);
|
|
312
|
+
padding: 0 var(--throttle-space-3);
|
|
313
|
+
min-height: var(--throttle-control-height);
|
|
314
|
+
color: var(--throttle-color-muted-foreground);
|
|
315
|
+
cursor: pointer;
|
|
316
|
+
transition: background var(--throttle-transition), color var(--throttle-transition);
|
|
317
|
+
}
|
|
318
|
+
.throttle-tabs__railitem:hover { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); }
|
|
319
|
+
.throttle-tabs__railitem:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
|
|
320
|
+
.throttle-tabs__tab:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
|
|
321
|
+
.throttle-tabs__railitem--active { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); font-weight: 500; }
|
|
322
|
+
.throttle-tabs__railicon { flex: none; opacity: .75; }
|
|
323
|
+
|
|
324
|
+
/* A CONTAINER query, not a viewport one: these components render inside a
|
|
325
|
+
merchant's page, so the viewport says nothing about how much room the
|
|
326
|
+
dashboard actually has — it could sit in a 400px column on a 1920px screen.
|
|
327
|
+
Below ~560px of container the rail becomes a full-width list above the
|
|
328
|
+
panel: all seven still visible, each still a 44px target. */
|
|
329
|
+
@container (max-width: 560px) {
|
|
330
|
+
/* align-items: flex-start is right in row direction and wrong in column —
|
|
331
|
+
it shrinks the rail to its content width, so width:100% alone cannot make
|
|
332
|
+
it span. Stretch it explicitly when stacked. */
|
|
333
|
+
.throttle-account { flex-direction: column; align-items: stretch; gap: var(--throttle-space-4); }
|
|
334
|
+
.throttle-tabs__rail { width: 100%; }
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.throttle-table { width: 100%; border-collapse: collapse; text-align: left; }
|
|
338
|
+
.throttle-table__head { color: var(--throttle-color-muted-foreground); font-weight: 500; font-size: var(--throttle-font-size-sm); }
|
|
339
|
+
.throttle-table__row { border-top: var(--throttle-border-width) solid var(--throttle-color-border); cursor: pointer; transition: background-color var(--throttle-transition); }
|
|
340
|
+
.throttle-table__row:hover { background: var(--throttle-color-surface); }
|
|
341
|
+
.throttle-table__row:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
|
|
342
|
+
.throttle-table__cell { padding: var(--throttle-space-3) var(--throttle-space-2); height: 56px; box-sizing: border-box; }
|
|
343
|
+
.throttle-table__scroll { overflow-x: auto; }
|
|
344
|
+
|
|
345
|
+
.throttle-empty { text-align: center; padding: var(--throttle-space-6) var(--throttle-space-4); }
|
|
346
|
+
.throttle-empty__title { font-weight: 600; margin: 0 0 var(--throttle-space-1); }
|
|
347
|
+
.throttle-empty__description { color: var(--throttle-color-muted-foreground); margin: 0; }
|
|
348
|
+
|
|
349
|
+
.throttle-avatar {
|
|
350
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
351
|
+
width: 32px; height: 32px; border-radius: var(--throttle-radius-full);
|
|
352
|
+
background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground);
|
|
353
|
+
font-size: var(--throttle-font-size-xs); font-weight: 600; text-transform: uppercase;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.throttle-badge {
|
|
357
|
+
display: inline-block; font-size: var(--throttle-font-size-xs); font-weight: 500;
|
|
358
|
+
padding: 2px var(--throttle-space-2); border-radius: var(--throttle-radius-full);
|
|
359
|
+
background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground);
|
|
360
|
+
}
|
|
361
|
+
.throttle-badge--muted { background: none; border: var(--throttle-border-width) solid var(--throttle-color-border); color: var(--throttle-color-muted-foreground); font-weight: 400; padding: 1px calc(var(--throttle-space-2) - 1px); }
|
|
362
|
+
.throttle-badge--success { background: color-mix(in srgb, var(--throttle-color-success) 15%, transparent); color: var(--throttle-color-success); }
|
|
363
|
+
.throttle-badge--warning { background: color-mix(in srgb, var(--throttle-color-warning) 15%, transparent); color: var(--throttle-color-warning); }
|
|
364
|
+
.throttle-badge--danger { background: color-mix(in srgb, var(--throttle-color-danger) 15%, transparent); color: var(--throttle-color-danger); }
|
|
365
|
+
|
|
366
|
+
.throttle-stack { display: flex; flex-direction: column; gap: var(--throttle-space-3); }
|
|
367
|
+
.throttle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2); flex-wrap: wrap; }
|
|
368
|
+
.throttle-muted { color: var(--throttle-color-muted-foreground); }
|
|
369
|
+
.throttle-banner {
|
|
370
|
+
display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2);
|
|
371
|
+
flex-wrap: wrap;
|
|
372
|
+
background: color-mix(in srgb, var(--throttle-color-warning) 12%, transparent);
|
|
373
|
+
color: var(--throttle-color-warning);
|
|
374
|
+
border-radius: var(--throttle-radius-sm);
|
|
375
|
+
padding: var(--throttle-space-3);
|
|
376
|
+
margin-bottom: var(--throttle-space-4);
|
|
377
|
+
}
|
|
378
|
+
.throttle-panel { display: flex; flex-direction: column; gap: var(--throttle-space-4); }
|
|
379
|
+
/* 100dvh, not 100vh: on mobile browsers 100vh includes the space behind the
|
|
380
|
+
dynamic address/toolbar, so the page would be taller than the visible
|
|
381
|
+
viewport and the card would sit off-centre until the toolbar collapsed. */
|
|
382
|
+
.throttle-page {
|
|
383
|
+
min-height: 100dvh;
|
|
384
|
+
width: 100%;
|
|
385
|
+
background: var(--throttle-color-surface);
|
|
386
|
+
display: flex;
|
|
387
|
+
flex-direction: column;
|
|
388
|
+
align-items: center;
|
|
389
|
+
justify-content: center;
|
|
390
|
+
gap: var(--throttle-space-5);
|
|
391
|
+
padding: var(--throttle-space-5);
|
|
392
|
+
}
|
|
393
|
+
.throttle-page__logo { display: flex; justify-content: center; }
|
|
394
|
+
.throttle-page__footer { color: var(--throttle-color-muted-foreground); text-align: center; font-size: var(--throttle-font-size-sm); }
|
|
395
|
+
.throttle-listitem {
|
|
396
|
+
display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);
|
|
397
|
+
flex-wrap: wrap;
|
|
398
|
+
border: var(--throttle-border-width) solid var(--throttle-color-border);
|
|
399
|
+
border-radius: var(--throttle-radius-sm);
|
|
400
|
+
padding: var(--throttle-space-3);
|
|
401
|
+
}
|
|
402
|
+
/* 'rows' variant: hairline rules between items instead of each item carrying
|
|
403
|
+
its own border+radius, so a panel does not read as a card inside a card. */
|
|
404
|
+
.throttle-rows { display: flex; flex-direction: column; }
|
|
405
|
+
.throttle-rowitem {
|
|
406
|
+
display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);
|
|
407
|
+
flex-wrap: wrap;
|
|
408
|
+
border-top: var(--throttle-border-width) solid var(--throttle-color-border);
|
|
409
|
+
padding: var(--throttle-space-3) 0;
|
|
410
|
+
}
|
|
411
|
+
.throttle-rowitem:first-child { border-top: none; padding-top: 0; }
|
|
412
|
+
.throttle-actions { display: flex; gap: var(--throttle-space-2); flex-wrap: wrap; }
|
|
413
|
+
.throttle-table__money { display: block; text-align: right; font-variant-numeric: tabular-nums; }
|
|
414
|
+
.throttle-lineitem { display: flex; align-items: center; gap: var(--throttle-space-3); padding: var(--throttle-space-2) 0; }
|
|
415
|
+
.throttle-lineitem__image { width: 48px; height: 48px; object-fit: cover; border-radius: var(--throttle-radius-sm); flex: none; }
|
|
416
|
+
.throttle-totals { display: flex; flex-direction: column; gap: var(--throttle-space-1); }
|
|
417
|
+
|
|
418
|
+
@media (prefers-reduced-motion: reduce) {
|
|
419
|
+
.throttle-button, .throttle-field__input, .throttle-dropdown__item, .throttle-dropdown__trigger, .throttle-tabs__tab, .throttle-table__row, .throttle-modal__close {
|
|
420
|
+
transition: none;
|
|
421
|
+
}
|
|
422
|
+
}
|