@farcaster/snap 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/catalog-renderer.d.ts +5 -5
- package/dist/react/catalog-renderer.js +16 -4
- package/dist/react/components/action-button.js +23 -5
- package/dist/react/index.d.ts +2 -1
- package/dist/react/snap-view-core.js +90 -25
- package/dist/react/v1/snap-view.js +1 -1
- package/dist/react/v2/snap-view.js +1 -1
- package/dist/react-native/components/snap-action-button.js +6 -1
- package/dist/react-native/snap-view-core.js +77 -24
- package/dist/react-native/types.d.ts +2 -1
- package/dist/render-state.d.ts +9 -0
- package/dist/render-state.js +27 -0
- package/dist/schemas.d.ts +123 -3
- package/dist/schemas.js +53 -2
- package/dist/server/parseRequest.js +19 -3
- package/dist/ui/button.d.ts +1 -0
- package/dist/ui/button.js +1 -0
- package/dist/ui/catalog.d.ts +13 -0
- package/dist/ui/catalog.js +15 -8
- package/package.json +1 -1
- package/src/index.ts +7 -0
- package/src/react/catalog-renderer.tsx +57 -3
- package/src/react/components/action-button.tsx +32 -3
- package/src/react/index.tsx +4 -1
- package/src/react/snap-view-core.tsx +144 -27
- package/src/react/v1/snap-view.tsx +1 -0
- package/src/react/v2/snap-view.tsx +1 -0
- package/src/react-native/components/snap-action-button.tsx +6 -1
- package/src/react-native/snap-view-core.tsx +114 -27
- package/src/react-native/types.ts +4 -1
- package/src/render-state.ts +46 -0
- package/src/schemas.ts +73 -2
- package/src/server/parseRequest.ts +37 -6
- package/src/ui/button.ts +1 -0
- package/src/ui/catalog.ts +16 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { useStateStore } from "@json-render/react";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { useStateStore, useStateValue } from "@json-render/react";
|
|
5
5
|
import { ExternalLink } from "lucide-react";
|
|
6
6
|
import { Button } from "@neynar/ui/button";
|
|
7
7
|
import { cn } from "@neynar/ui/utils";
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getPaginatorAction,
|
|
11
11
|
runPaginatorAction,
|
|
12
12
|
} from "../../ui/paginator-state";
|
|
13
|
+
import { buildActionActivityStateChanges } from "../../render-state";
|
|
13
14
|
import { useSnapStackDirection } from "../stack-direction-context";
|
|
14
15
|
import { ICON_MAP } from "./icon";
|
|
15
16
|
|
|
@@ -24,6 +25,22 @@ function isExternalLinkAction(
|
|
|
24
25
|
return press.action === "open_url";
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
function getActionPendingPath(on: Record<string, unknown> | undefined) {
|
|
29
|
+
const press = on?.press as
|
|
30
|
+
| { action?: unknown; params?: Record<string, unknown> }
|
|
31
|
+
| undefined;
|
|
32
|
+
if (!press?.action) return "/__snap/action/pending";
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
buildActionActivityStateChanges({
|
|
36
|
+
actionName: press.action,
|
|
37
|
+
params: press.params ?? {},
|
|
38
|
+
pending: true,
|
|
39
|
+
}).find((change) => change.path.endsWith("/pending"))?.path ??
|
|
40
|
+
"/__snap/action/pending"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
export function SnapActionButton({
|
|
28
45
|
element,
|
|
29
46
|
emit,
|
|
@@ -38,18 +55,25 @@ export function SnapActionButton({
|
|
|
38
55
|
const label = String(props.label ?? "Action");
|
|
39
56
|
const variant = String(props.variant ?? "secondary");
|
|
40
57
|
const isPrimary = variant === "primary";
|
|
58
|
+
const disabled = props.disabled === true;
|
|
41
59
|
const iconName = props.icon ? String(props.icon) : undefined;
|
|
42
60
|
const colors = useSnapColors();
|
|
43
61
|
const [hovered, setHovered] = useState(false);
|
|
44
62
|
const stateStore = useStateStore();
|
|
45
63
|
const paginatorAction = getPaginatorAction(element.on);
|
|
64
|
+
const actionPendingPath = useMemo(
|
|
65
|
+
() => getActionPendingPath(element.on),
|
|
66
|
+
[element.on],
|
|
67
|
+
);
|
|
68
|
+
const actionPending = useStateValue(actionPendingPath) === true;
|
|
46
69
|
|
|
47
70
|
const Icon = iconName ? ICON_MAP[iconName] : undefined;
|
|
48
71
|
const showExternalIcon = isExternalLinkAction(element.on);
|
|
49
72
|
const inHorizontalStack = useSnapStackDirection() === "horizontal";
|
|
50
73
|
|
|
51
74
|
const style = {
|
|
52
|
-
cursor: "pointer" as const,
|
|
75
|
+
cursor: disabled ? ("not-allowed" as const) : ("pointer" as const),
|
|
76
|
+
opacity: disabled ? 0.62 : 1,
|
|
53
77
|
...(isPrimary
|
|
54
78
|
? {
|
|
55
79
|
backgroundColor: hovered ? colors.accentHover : colors.accent,
|
|
@@ -83,8 +107,10 @@ export function SnapActionButton({
|
|
|
83
107
|
type="button"
|
|
84
108
|
variant={isPrimary ? "default" : "secondary"}
|
|
85
109
|
className={cn("h-8 w-full gap-2 px-3 text-sm")}
|
|
110
|
+
disabled={disabled}
|
|
86
111
|
style={style}
|
|
87
112
|
onClick={() => {
|
|
113
|
+
if (disabled) return;
|
|
88
114
|
if (!runPaginatorAction(stateStore, paginatorAction)) {
|
|
89
115
|
emit("press");
|
|
90
116
|
}
|
|
@@ -94,6 +120,9 @@ export function SnapActionButton({
|
|
|
94
120
|
>
|
|
95
121
|
{Icon && <Icon size={16} />}
|
|
96
122
|
{label}
|
|
123
|
+
{actionPending && (
|
|
124
|
+
<span data-snap-action-pending-active="true" hidden />
|
|
125
|
+
)}
|
|
97
126
|
{showExternalIcon && (
|
|
98
127
|
<ExternalLink size={14} style={{ opacity: 0.6 }} />
|
|
99
128
|
)}
|
package/src/react/index.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import type { ReactNode } from "react";
|
|
|
5
5
|
import type { ValidationResult } from "../validator.js";
|
|
6
6
|
import { SPEC_VERSION_2 } from "../constants";
|
|
7
7
|
import type { SnapRenderState } from "../render-state";
|
|
8
|
+
import type { SnapTransactionResult } from "../schemas";
|
|
8
9
|
import { SnapCardV1 } from "./v1/snap-view";
|
|
9
10
|
import { SnapCardV2 } from "./v2/snap-view";
|
|
10
11
|
|
|
@@ -57,7 +58,9 @@ export type SnapActionHandlers = {
|
|
|
57
58
|
recipientAddress?: string;
|
|
58
59
|
}) => void;
|
|
59
60
|
swap_token: (params: { sellToken?: string; buyToken?: string }) => void;
|
|
60
|
-
send_transaction?: (
|
|
61
|
+
send_transaction?: (
|
|
62
|
+
params: SnapSendTransactionParams,
|
|
63
|
+
) => void | Promise<void | SnapTransactionResult>;
|
|
61
64
|
};
|
|
62
65
|
|
|
63
66
|
export type { SnapRenderState };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { Spec } from "@json-render/core";
|
|
4
|
+
import { createStateStore } from "@json-render/react";
|
|
4
5
|
import { snapJsonRenderCatalog } from "../ui/index.js";
|
|
5
6
|
import { SnapCatalogView } from "./catalog-renderer";
|
|
6
7
|
import { SnapPreviewAccentProvider } from "./accent-context";
|
|
@@ -8,10 +9,11 @@ import { SnapVersionProvider } from "./snap-version-context";
|
|
|
8
9
|
import { resolveSnapPaletteHex } from "./lib/resolve-palette-hex";
|
|
9
10
|
import { snapPreviewPrimaryCssProperties } from "./lib/preview-primary-css";
|
|
10
11
|
import {
|
|
11
|
-
|
|
12
|
+
buildActionActivityStateChanges,
|
|
12
13
|
buildInitialRenderState,
|
|
13
14
|
cloneSnapRenderState,
|
|
14
15
|
getUnpresentedSnapEffects,
|
|
16
|
+
hasPendingSnapAction,
|
|
15
17
|
markSnapEffectsPresented,
|
|
16
18
|
type SnapRenderState,
|
|
17
19
|
} from "../render-state";
|
|
@@ -36,6 +38,12 @@ function optionalString(value: unknown): string | undefined {
|
|
|
36
38
|
return value ? String(value) : undefined;
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
function recordValue(value: unknown): Record<string, unknown> | undefined {
|
|
42
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
43
|
+
? (value as Record<string, unknown>)
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
function withDefaultElementProps(spec: Spec): Spec {
|
|
40
48
|
if (!spec || typeof spec !== "object" || !("elements" in spec)) return spec;
|
|
41
49
|
const elements = spec.elements as unknown as Record<
|
|
@@ -266,6 +274,8 @@ export function SnapLoadingOverlay({
|
|
|
266
274
|
|
|
267
275
|
return (
|
|
268
276
|
<div
|
|
277
|
+
data-snap-loading-overlay
|
|
278
|
+
data-snap-loading-active={active ? "true" : "false"}
|
|
269
279
|
style={{
|
|
270
280
|
position: "absolute",
|
|
271
281
|
inset: 0,
|
|
@@ -299,6 +309,20 @@ export function SnapLoadingOverlay({
|
|
|
299
309
|
}}
|
|
300
310
|
/>
|
|
301
311
|
<style>{`
|
|
312
|
+
[data-snap-view-root]:has([data-snap-action-pending-active="true"])
|
|
313
|
+
[data-snap-loading-overlay] {
|
|
314
|
+
opacity: 1 !important;
|
|
315
|
+
pointer-events: auto !important;
|
|
316
|
+
backdrop-filter: blur(10px) saturate(1.05) !important;
|
|
317
|
+
-webkit-backdrop-filter: blur(10px) saturate(1.05) !important;
|
|
318
|
+
}
|
|
319
|
+
[data-snap-card-surface]:has([data-snap-action-pending-active="true"])
|
|
320
|
+
> [data-snap-loading-overlay] {
|
|
321
|
+
opacity: 1 !important;
|
|
322
|
+
pointer-events: auto !important;
|
|
323
|
+
backdrop-filter: blur(10px) saturate(1.05) !important;
|
|
324
|
+
-webkit-backdrop-filter: blur(10px) saturate(1.05) !important;
|
|
325
|
+
}
|
|
302
326
|
@keyframes snapViewSpin {
|
|
303
327
|
to { transform: rotate(360deg); }
|
|
304
328
|
}
|
|
@@ -314,6 +338,22 @@ export function SnapLoadingOverlay({
|
|
|
314
338
|
);
|
|
315
339
|
}
|
|
316
340
|
|
|
341
|
+
function SnapPendingActionOverlay({
|
|
342
|
+
appearance,
|
|
343
|
+
accentHex,
|
|
344
|
+
}: {
|
|
345
|
+
appearance: "light" | "dark";
|
|
346
|
+
accentHex: string;
|
|
347
|
+
}) {
|
|
348
|
+
return (
|
|
349
|
+
<SnapLoadingOverlay
|
|
350
|
+
appearance={appearance}
|
|
351
|
+
accentHex={accentHex}
|
|
352
|
+
active={false}
|
|
353
|
+
/>
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
317
357
|
const PALETTE = [
|
|
318
358
|
"gray",
|
|
319
359
|
"blue",
|
|
@@ -360,12 +400,31 @@ export function SnapViewCore({
|
|
|
360
400
|
[initialRenderState, spec.state, snap.theme?.accent],
|
|
361
401
|
);
|
|
362
402
|
|
|
403
|
+
const stateStore = useMemo(() => createStateStore(initialState), [
|
|
404
|
+
initialState,
|
|
405
|
+
]);
|
|
363
406
|
const stateRef = useRef<Record<string, unknown>>(initialState);
|
|
407
|
+
const onRenderStateChangeRef = useRef(onRenderStateChange);
|
|
408
|
+
const pendingActionCountRef = useRef(0);
|
|
364
409
|
|
|
365
410
|
useEffect(() => {
|
|
366
411
|
stateRef.current = cloneSnapRenderState(initialState);
|
|
367
412
|
}, [initialState]);
|
|
368
413
|
|
|
414
|
+
useEffect(() => {
|
|
415
|
+
onRenderStateChangeRef.current = onRenderStateChange;
|
|
416
|
+
}, [onRenderStateChange]);
|
|
417
|
+
|
|
418
|
+
useEffect(
|
|
419
|
+
() =>
|
|
420
|
+
stateStore.subscribe(() => {
|
|
421
|
+
const snapshot = cloneSnapRenderState(stateStore.getSnapshot());
|
|
422
|
+
stateRef.current = snapshot;
|
|
423
|
+
onRenderStateChangeRef.current?.(snapshot);
|
|
424
|
+
}),
|
|
425
|
+
[stateStore],
|
|
426
|
+
);
|
|
427
|
+
|
|
369
428
|
useEffect(() => {
|
|
370
429
|
const catalogResult = snapJsonRenderCatalog.validate(spec);
|
|
371
430
|
if (!catalogResult.success) {
|
|
@@ -390,10 +449,6 @@ export function SnapViewCore({
|
|
|
390
449
|
confetti: 0,
|
|
391
450
|
fireworks: 0,
|
|
392
451
|
});
|
|
393
|
-
const onRenderStateChangeRef = useRef(onRenderStateChange);
|
|
394
|
-
useEffect(() => {
|
|
395
|
-
onRenderStateChangeRef.current = onRenderStateChange;
|
|
396
|
-
}, [onRenderStateChange]);
|
|
397
452
|
useEffect(() => {
|
|
398
453
|
const effectsToPresent = getUnpresentedSnapEffects(
|
|
399
454
|
stateRef.current,
|
|
@@ -415,7 +470,10 @@ export function SnapViewCore({
|
|
|
415
470
|
}
|
|
416
471
|
|
|
417
472
|
if (markSnapEffectsPresented(stateRef.current, effectsToPresent)) {
|
|
418
|
-
|
|
473
|
+
const meta = recordValue(stateRef.current.__snapRender);
|
|
474
|
+
stateStore.update({
|
|
475
|
+
"/__snapRender/presentedEffects": meta?.presentedEffects ?? [],
|
|
476
|
+
});
|
|
419
477
|
}
|
|
420
478
|
|
|
421
479
|
setEffectRunKeys((current) => ({
|
|
@@ -430,7 +488,7 @@ export function SnapViewCore({
|
|
|
430
488
|
? current.fireworks
|
|
431
489
|
: 0,
|
|
432
490
|
}));
|
|
433
|
-
}, [initialState, showConfetti, showFireworks, snapEffects]);
|
|
491
|
+
}, [initialState, showConfetti, showFireworks, snapEffects, stateStore]);
|
|
434
492
|
|
|
435
493
|
const accentName = snap.theme?.accent ?? "purple";
|
|
436
494
|
|
|
@@ -449,6 +507,40 @@ export function SnapViewCore({
|
|
|
449
507
|
} as CSSProperties;
|
|
450
508
|
}, [accentName, appearance]);
|
|
451
509
|
|
|
510
|
+
const applyActionActivityState = useCallback(
|
|
511
|
+
(name: unknown, params: Record<string, unknown>, pending: boolean) => {
|
|
512
|
+
stateStore.update(
|
|
513
|
+
Object.fromEntries(
|
|
514
|
+
buildActionActivityStateChanges({
|
|
515
|
+
actionName: name,
|
|
516
|
+
params,
|
|
517
|
+
pending,
|
|
518
|
+
}).map(({ path, value }) => [path, value]),
|
|
519
|
+
),
|
|
520
|
+
);
|
|
521
|
+
},
|
|
522
|
+
[stateStore],
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
const setActionPending = useCallback(
|
|
526
|
+
(name: unknown, params: Record<string, unknown>) => {
|
|
527
|
+
pendingActionCountRef.current += 1;
|
|
528
|
+
applyActionActivityState(name, params, true);
|
|
529
|
+
},
|
|
530
|
+
[applyActionActivityState],
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const setActionSettled = useCallback(
|
|
534
|
+
(name: unknown, params: Record<string, unknown>) => {
|
|
535
|
+
pendingActionCountRef.current = Math.max(
|
|
536
|
+
0,
|
|
537
|
+
pendingActionCountRef.current - 1,
|
|
538
|
+
);
|
|
539
|
+
applyActionActivityState(name, params, false);
|
|
540
|
+
},
|
|
541
|
+
[applyActionActivityState],
|
|
542
|
+
);
|
|
543
|
+
|
|
452
544
|
const handleAction = useCallback(
|
|
453
545
|
(name: unknown, params: unknown) => {
|
|
454
546
|
const inputs = (stateRef.current.inputs ?? {}) as Record<
|
|
@@ -456,30 +548,35 @@ export function SnapViewCore({
|
|
|
456
548
|
JsonValue
|
|
457
549
|
>;
|
|
458
550
|
const p = (params ?? {}) as Record<string, unknown>;
|
|
551
|
+
let result: unknown;
|
|
552
|
+
setActionPending(name, p);
|
|
553
|
+
|
|
459
554
|
switch (name) {
|
|
460
555
|
case "submit":
|
|
461
|
-
handlers.submit(String(p.target ?? ""), inputs);
|
|
556
|
+
result = handlers.submit(String(p.target ?? ""), inputs);
|
|
462
557
|
break;
|
|
463
558
|
case "open_url":
|
|
464
|
-
handlers.open_url(String(p.target ?? ""));
|
|
559
|
+
result = handlers.open_url(String(p.target ?? ""));
|
|
465
560
|
break;
|
|
466
561
|
case "open_snap":
|
|
467
|
-
handlers.open_snap(String(p.target ?? ""));
|
|
562
|
+
result = handlers.open_snap(String(p.target ?? ""));
|
|
468
563
|
break;
|
|
469
564
|
case "open_mini_app":
|
|
470
|
-
handlers.open_mini_app(String(p.target ?? ""));
|
|
565
|
+
result = handlers.open_mini_app(String(p.target ?? ""));
|
|
471
566
|
break;
|
|
472
567
|
case "view_cast":
|
|
473
|
-
handlers.view_cast({ hash: String(p.hash ?? "") });
|
|
568
|
+
result = handlers.view_cast({ hash: String(p.hash ?? "") });
|
|
474
569
|
break;
|
|
475
570
|
case "view_profile":
|
|
476
|
-
handlers.view_profile({ fid: Number(p.fid ?? 0) });
|
|
571
|
+
result = handlers.view_profile({ fid: Number(p.fid ?? 0) });
|
|
477
572
|
break;
|
|
478
573
|
case "view_channel":
|
|
479
|
-
handlers.view_channel({
|
|
574
|
+
result = handlers.view_channel({
|
|
575
|
+
channelKey: String(p.channelKey ?? ""),
|
|
576
|
+
});
|
|
480
577
|
break;
|
|
481
578
|
case "compose_cast":
|
|
482
|
-
handlers.compose_cast({
|
|
579
|
+
result = handlers.compose_cast({
|
|
483
580
|
text: p.text ? String(p.text) : undefined,
|
|
484
581
|
channelKey: p.channelKey ? String(p.channelKey) : undefined,
|
|
485
582
|
embeds: Array.isArray(p.embeds)
|
|
@@ -488,10 +585,10 @@ export function SnapViewCore({
|
|
|
488
585
|
});
|
|
489
586
|
break;
|
|
490
587
|
case "view_token":
|
|
491
|
-
handlers.view_token({ token: String(p.token ?? "") });
|
|
588
|
+
result = handlers.view_token({ token: String(p.token ?? "") });
|
|
492
589
|
break;
|
|
493
590
|
case "send_token":
|
|
494
|
-
handlers.send_token({
|
|
591
|
+
result = handlers.send_token({
|
|
495
592
|
token: String(p.token ?? ""),
|
|
496
593
|
amount: p.amount ? String(p.amount) : undefined,
|
|
497
594
|
recipientFid: p.recipientFid ? Number(p.recipientFid) : undefined,
|
|
@@ -501,13 +598,13 @@ export function SnapViewCore({
|
|
|
501
598
|
});
|
|
502
599
|
break;
|
|
503
600
|
case "swap_token":
|
|
504
|
-
handlers.swap_token({
|
|
601
|
+
result = handlers.swap_token({
|
|
505
602
|
sellToken: p.sellToken ? String(p.sellToken) : undefined,
|
|
506
603
|
buyToken: p.buyToken ? String(p.buyToken) : undefined,
|
|
507
604
|
});
|
|
508
605
|
break;
|
|
509
606
|
case "send_transaction":
|
|
510
|
-
handlers.send_transaction?.({
|
|
607
|
+
result = handlers.send_transaction?.({
|
|
511
608
|
chainId: String(p.chainId ?? ""),
|
|
512
609
|
to: String(p.to ?? ""),
|
|
513
610
|
data: optionalString(p.data),
|
|
@@ -521,12 +618,29 @@ export function SnapViewCore({
|
|
|
521
618
|
default:
|
|
522
619
|
break;
|
|
523
620
|
}
|
|
621
|
+
|
|
622
|
+
if (result instanceof Promise) {
|
|
623
|
+
void result.finally(() => {
|
|
624
|
+
setActionSettled(name, p);
|
|
625
|
+
}).catch(() => {});
|
|
626
|
+
} else {
|
|
627
|
+
setActionSettled(name, p);
|
|
628
|
+
}
|
|
629
|
+
return result;
|
|
524
630
|
},
|
|
525
|
-
[handlers],
|
|
631
|
+
[handlers, setActionPending, setActionSettled],
|
|
526
632
|
);
|
|
527
633
|
|
|
528
634
|
return (
|
|
529
|
-
<div
|
|
635
|
+
<div
|
|
636
|
+
data-snap-view-root
|
|
637
|
+
style={{ position: "relative", width: "100%" }}
|
|
638
|
+
onClickCapture={(event) => {
|
|
639
|
+
if (!hasPendingSnapAction(stateRef.current)) return;
|
|
640
|
+
event.preventDefault();
|
|
641
|
+
event.stopPropagation();
|
|
642
|
+
}}
|
|
643
|
+
>
|
|
530
644
|
{showConfetti && effectRunKeys.confetti > 0 && (
|
|
531
645
|
<ConfettiOverlay key={effectRunKeys.confetti} />
|
|
532
646
|
)}
|
|
@@ -552,14 +666,17 @@ export function SnapViewCore({
|
|
|
552
666
|
<SnapCatalogView
|
|
553
667
|
key={pageKey}
|
|
554
668
|
spec={spec}
|
|
555
|
-
|
|
669
|
+
store={stateStore}
|
|
556
670
|
loading={false}
|
|
557
|
-
onStateChange={(changes) => {
|
|
558
|
-
applyStatePaths(stateRef.current, changes);
|
|
559
|
-
onRenderStateChange?.(cloneSnapRenderState(stateRef.current));
|
|
560
|
-
}}
|
|
561
671
|
onAction={handleAction}
|
|
562
|
-
|
|
672
|
+
>
|
|
673
|
+
{loadingOverlay === undefined ? (
|
|
674
|
+
<SnapPendingActionOverlay
|
|
675
|
+
appearance={appearance}
|
|
676
|
+
accentHex={accentHex}
|
|
677
|
+
/>
|
|
678
|
+
) : null}
|
|
679
|
+
</SnapCatalogView>
|
|
563
680
|
</SnapVersionProvider>
|
|
564
681
|
</SnapPreviewAccentProvider>
|
|
565
682
|
</div>
|
|
@@ -34,6 +34,7 @@ export function SnapActionButton({
|
|
|
34
34
|
const label = String(props.label ?? "Action");
|
|
35
35
|
const variant = String(props.variant ?? "secondary");
|
|
36
36
|
const isPrimary = variant === "primary";
|
|
37
|
+
const disabled = props.disabled === true;
|
|
37
38
|
const iconName = props.icon ? String(props.icon) : undefined;
|
|
38
39
|
|
|
39
40
|
const textColor = isPrimary ? "#fff" : colors.text;
|
|
@@ -53,10 +54,13 @@ export function SnapActionButton({
|
|
|
53
54
|
isPrimary ? styles.btnDefault : styles.btnOther,
|
|
54
55
|
isPrimary
|
|
55
56
|
? { backgroundColor: pressed ? accentHex + "DD" : accentHex }
|
|
56
|
-
|
|
57
|
+
: { backgroundColor: pressed ? colors.mutedHover : colors.muted },
|
|
57
58
|
pressed && styles.pressed,
|
|
59
|
+
disabled && styles.disabled,
|
|
58
60
|
]}
|
|
61
|
+
disabled={disabled}
|
|
59
62
|
onPress={() => {
|
|
63
|
+
if (disabled) return;
|
|
60
64
|
if (runPaginatorAction(stateStore, paginatorAction)) return;
|
|
61
65
|
void (async () => {
|
|
62
66
|
try {
|
|
@@ -114,4 +118,5 @@ const styles = StyleSheet.create({
|
|
|
114
118
|
paddingVertical: 6,
|
|
115
119
|
},
|
|
116
120
|
pressed: { opacity: 0.88 },
|
|
121
|
+
disabled: { opacity: 0.62 },
|
|
117
122
|
});
|