@farcaster/snap 2.0.3 → 2.1.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.
@@ -1,12 +1,13 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from "react";
3
- import { Platform, StyleSheet, Text, View } from "react-native";
3
+ import { Platform, Pressable, StyleSheet, Text, View } from "react-native";
4
4
  import { SnapThemeProvider, useSnapTheme } from "../theme.js";
5
5
  import { SnapLoadingOverlay, SnapViewCoreInner, resolveAccentHex, } from "../snap-view-core.js";
6
6
  import { validateSnapResponse, } from "@farcaster/snap";
7
7
  // ─── Constants ───────────────────────────────────────
8
8
  const SNAP_MAX_HEIGHT = 500;
9
9
  const SNAP_WARNING_HEIGHT = 700;
10
+ const SHOW_MORE_OVERHANG = 14;
10
11
  // ─── Validation fallback ─────────────────────────────
11
12
  function SnapValidationFallback({ message }) {
12
13
  const { colors } = useSnapTheme();
@@ -54,28 +55,63 @@ function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWa
54
55
  const { colors, mode } = useSnapTheme();
55
56
  const accentHex = resolveAccentHex(snap.theme?.accent, mode);
56
57
  const [contentHeight, setContentHeight] = useState(0);
58
+ const [isExpanded, setIsExpanded] = useState(false);
59
+ useEffect(() => {
60
+ setIsExpanded(false);
61
+ setContentHeight(0);
62
+ }, [snap]);
63
+ const isExpandable = !showOverflowWarning && contentHeight > SNAP_MAX_HEIGHT + 1;
64
+ const isClipped = isExpandable && !isExpanded;
57
65
  const content = (_jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, loadingOverlay: null }));
58
66
  if (plain) {
59
- return (_jsxs(_Fragment, { children: [content, loading
67
+ return (_jsxs(_Fragment, { children: [_jsx(View, { style: isClipped ? { maxHeight: SNAP_MAX_HEIGHT, overflow: "hidden" } : undefined, children: _jsx(View, { collapsable: false, onLayout: (e) => {
68
+ const nextHeight = Math.round(e.nativeEvent.layout.height);
69
+ setContentHeight((current) => isClipped
70
+ ? Math.max(current, nextHeight)
71
+ : current === nextHeight
72
+ ? current
73
+ : nextHeight);
74
+ }, children: content }) }), loading
60
75
  ? loadingOverlay === undefined
61
76
  ? _jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex })
62
77
  : loadingOverlay
63
- : null] }));
78
+ : null, isExpandable ? (_jsx(View, { style: [cardStyles.expandRow, cardStyles.expandRowPlain], children: _jsx(Pressable, { style: ({ pressed }) => [
79
+ cardStyles.expandButton,
80
+ {
81
+ backgroundColor: pressed ? colors.mutedHover : colors.muted,
82
+ },
83
+ ], onPress: () => setIsExpanded((value) => !value), children: _jsx(Text, { style: [cardStyles.expandButtonText, { color: colors.text }], children: isExpanded ? "Show less" : "Show more" }) }) })) : null] }));
64
84
  }
65
85
  const overflowAmount = showOverflowWarning ? contentHeight - SNAP_MAX_HEIGHT : 0;
66
- return (_jsxs(_Fragment, { children: [_jsxs(View, { style: {
67
- borderRadius,
68
- borderWidth: 1,
69
- borderColor: colors.border,
70
- backgroundColor: colors.surface,
71
- maxHeight: showOverflowWarning ? undefined : SNAP_MAX_HEIGHT,
72
- overflow: "hidden",
73
- minHeight: 120,
74
- }, children: [_jsx(View, { collapsable: false, onLayout: (e) => setContentHeight(Math.round(e.nativeEvent.layout.height)), style: { paddingHorizontal: 16, paddingVertical: 16 }, children: content }), showOverflowWarning && contentHeight > SNAP_MAX_HEIGHT && (_jsxs(View, { style: { position: "absolute", top: SNAP_MAX_HEIGHT, left: 0, right: 0, height: overflowAmount, zIndex: 10, pointerEvents: "none" }, children: [_jsx(View, { style: { height: 1, borderTopWidth: 1, borderStyle: "dashed", borderColor: "rgba(255,100,100,0.6)" } }), _jsx(View, { style: { position: "absolute", top: -10, right: 4, backgroundColor: "rgba(0,0,0,0.7)", paddingHorizontal: 4, paddingVertical: 1, borderRadius: 3 }, children: _jsxs(Text, { style: { fontSize: 10, color: "rgba(255,100,100,0.7)", fontFamily: Platform.select({ ios: "Menlo", default: "monospace" }) }, children: [SNAP_MAX_HEIGHT, "px"] }) }), _jsx(View, { style: { flex: 1, backgroundColor: "rgba(255,50,50,0.15)" } })] })), loading
75
- ? loadingOverlay === undefined
76
- ? _jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex })
77
- : loadingOverlay
78
- : null] }), actionError && (_jsx(Text, { style: {
86
+ const isDark = mode === "dark";
87
+ const pillBg = isDark ? "rgba(40,40,40,0.92)" : "rgba(255,255,255,0.92)";
88
+ const pillBgPressed = isDark ? "rgba(60,60,60,0.95)" : "rgba(240,240,240,0.95)";
89
+ return (_jsxs(View, { style: { paddingBottom: isExpandable ? SHOW_MORE_OVERHANG : 0 }, children: [_jsxs(View, { style: { position: "relative" }, children: [_jsxs(View, { style: {
90
+ borderRadius,
91
+ borderWidth: 1,
92
+ borderColor: colors.border,
93
+ backgroundColor: colors.surface,
94
+ maxHeight: showOverflowWarning ? undefined : isClipped ? SNAP_MAX_HEIGHT : undefined,
95
+ overflow: "hidden",
96
+ minHeight: 120,
97
+ }, children: [_jsx(View, { collapsable: false, onLayout: (e) => {
98
+ const nextHeight = Math.round(e.nativeEvent.layout.height);
99
+ setContentHeight((current) => isClipped
100
+ ? Math.max(current, nextHeight)
101
+ : current === nextHeight
102
+ ? current
103
+ : nextHeight);
104
+ }, style: { paddingHorizontal: 16, paddingVertical: 16 }, children: content }), showOverflowWarning && contentHeight > SNAP_MAX_HEIGHT && (_jsxs(View, { style: { position: "absolute", top: SNAP_MAX_HEIGHT, left: 0, right: 0, height: overflowAmount, zIndex: 10, pointerEvents: "none" }, children: [_jsx(View, { style: { height: 1, borderTopWidth: 1, borderStyle: "dashed", borderColor: "rgba(255,100,100,0.6)" } }), _jsx(View, { style: { position: "absolute", top: -10, right: 4, backgroundColor: "rgba(0,0,0,0.7)", paddingHorizontal: 4, paddingVertical: 1, borderRadius: 3 }, children: _jsxs(Text, { style: { fontSize: 10, color: "rgba(255,100,100,0.7)", fontFamily: Platform.select({ ios: "Menlo", default: "monospace" }) }, children: [SNAP_MAX_HEIGHT, "px"] }) }), _jsx(View, { style: { flex: 1, backgroundColor: "rgba(255,50,50,0.15)" } })] })), loading
105
+ ? loadingOverlay === undefined
106
+ ? _jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex })
107
+ : loadingOverlay
108
+ : null] }), isExpandable ? (_jsx(View, { pointerEvents: "box-none", style: cardStyles.expandFloat, children: _jsx(Pressable, { style: ({ pressed }) => [
109
+ cardStyles.expandButton,
110
+ {
111
+ backgroundColor: pressed ? pillBgPressed : pillBg,
112
+ borderColor: colors.border,
113
+ },
114
+ ], onPress: () => setIsExpanded((value) => !value), children: _jsx(Text, { style: [cardStyles.expandButtonText, { color: colors.text }], children: isExpanded ? "Show less" : "Show more" }) }) })) : null] }), actionError && (_jsx(Text, { style: {
79
115
  paddingHorizontal: 12,
80
116
  paddingVertical: 8,
81
117
  fontSize: 13,
@@ -92,6 +128,33 @@ const cardStyles = StyleSheet.create({
92
128
  card: { borderWidth: 1, minHeight: 120, overflow: "hidden" },
93
129
  body: { paddingHorizontal: 16, paddingVertical: 16 },
94
130
  actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
131
+ expandFloat: {
132
+ position: "absolute",
133
+ left: 0,
134
+ right: 0,
135
+ bottom: -14,
136
+ height: 28,
137
+ alignItems: "center",
138
+ justifyContent: "center",
139
+ },
140
+ expandRowPlain: {
141
+ paddingTop: 8,
142
+ alignItems: "center",
143
+ },
144
+ expandButton: {
145
+ minWidth: 92,
146
+ alignItems: "center",
147
+ justifyContent: "center",
148
+ borderRadius: 9999,
149
+ borderWidth: 1,
150
+ paddingHorizontal: 10,
151
+ paddingVertical: 4,
152
+ },
153
+ expandButtonText: {
154
+ fontSize: 12,
155
+ lineHeight: 16,
156
+ fontWeight: "600",
157
+ },
95
158
  warningOverlay: {
96
159
  position: "absolute",
97
160
  top: SNAP_MAX_HEIGHT,
@@ -90,7 +90,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
90
90
  },
91
91
  cell_grid: {
92
92
  props: cellGridProps,
93
- description: "Cell grid — sparse colored cells on a rows×cols grid. Optional gap and selection mode (taps write to inputs[name]).",
93
+ description: "Cell grid — sparse colored cells on a rows×cols grid. Two interaction modes: leave select 'off' and bind on.press to fire an action per cell press (inputs[name] is the pressed 'row,col' before the action runs); or set select 'single'/'multiple' for press-to-select with a visual ring (no auto-fire — pair with a separate submit button). on.press is ignored when select is on.",
94
94
  },
95
95
  },
96
96
  actions: {
package/dist/validator.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { snapResponseSchema } from "./schemas.js";
2
- import { MAX_CHILDREN, MAX_DEPTH, MAX_ELEMENTS, MAX_ROOT_CHILDREN, SPEC_VERSION_1 } from "./constants.js";
2
+ import { MAX_CHILDREN, MAX_DEPTH, MAX_ELEMENTS, MAX_ROOT_CHILDREN, SPEC_VERSION_1, } from "./constants.js";
3
3
  import { snapJsonRenderCatalog } from "./ui/catalog.js";
4
4
  // ─── Helpers ──────────────────────────────────────────
5
5
  /** Actions whose `params.target` must be a valid URL. */
@@ -9,8 +9,6 @@ const URL_TARGET_ACTIONS = new Set([
9
9
  "open_snap",
10
10
  "open_mini_app",
11
11
  ]);
12
- /** Image file extensions allowed in image URLs. */
13
- const ALLOWED_IMAGE_EXTENSIONS = new Set(["jpg", "jpeg", "png", "gif", "webp"]);
14
12
  /**
15
13
  * Returns true if the URL is a loopback address (localhost dev exception).
16
14
  */
@@ -38,31 +36,6 @@ function validateUrl(raw) {
38
36
  return `javascript: URIs are not allowed`;
39
37
  return `URL must use HTTPS (got ${url.protocol.replace(":", "")}): "${raw}"`;
40
38
  }
41
- /**
42
- * Validate an image URL: must pass URL validation + have an allowed extension.
43
- */
44
- function validateImageUrl(raw) {
45
- const urlError = validateUrl(raw);
46
- if (urlError)
47
- return urlError;
48
- let url;
49
- try {
50
- url = new URL(raw);
51
- }
52
- catch {
53
- return null; // already caught above
54
- }
55
- const pathname = url.pathname;
56
- const lastDot = pathname.lastIndexOf(".");
57
- if (lastDot === -1) {
58
- return `Image URL must end with a supported extension (${[...ALLOWED_IMAGE_EXTENSIONS].join(", ")}): "${raw}"`;
59
- }
60
- const ext = pathname.slice(lastDot + 1).toLowerCase();
61
- if (!ALLOWED_IMAGE_EXTENSIONS.has(ext)) {
62
- return `Image URL has unsupported extension ".${ext}" (allowed: ${[...ALLOWED_IMAGE_EXTENSIONS].join(", ")}): "${raw}"`;
63
- }
64
- return null;
65
- }
66
39
  // ─── Depth measurement ────────────────────────────────
67
40
  /**
68
41
  * Walk the element tree from `root` and return the max depth reached.
@@ -133,8 +106,8 @@ function validateStructure(ui) {
133
106
  // ─── URL validation ───────────────────────────────────
134
107
  /**
135
108
  * Validate all URLs in the snap:
136
- * - image.url: must be HTTPS with allowed extension
137
- * - action target URLs (submit, open_url, open_snap, open_mini_app): must be HTTPS
109
+ * - image.url: must use HTTPS (or HTTP on loopback for dev)
110
+ * - action target URLs (submit, open_url, open_snap, open_mini_app): must use HTTPS (or HTTP on loopback for dev)
138
111
  */
139
112
  function validateUrls(elements) {
140
113
  const issues = [];
@@ -142,7 +115,7 @@ function validateUrls(elements) {
142
115
  for (const [id, el] of Object.entries(els)) {
143
116
  // Validate image URLs
144
117
  if (el.type === "image" && typeof el.props?.url === "string") {
145
- const error = validateImageUrl(el.props.url);
118
+ const error = validateUrl(el.props.url);
146
119
  if (error) {
147
120
  issues.push({
148
121
  code: "custom",
@@ -191,11 +164,13 @@ export function validateSnapResponse(json) {
191
164
  if (!(ui.root in ui.elements)) {
192
165
  return {
193
166
  valid: false,
194
- issues: [{
167
+ issues: [
168
+ {
195
169
  code: "custom",
196
170
  message: `ui.root "${ui.root}" does not exist in ui.elements`,
197
171
  path: ["ui", "root"],
198
- }],
172
+ },
173
+ ],
199
174
  };
200
175
  }
201
176
  // Structural limits and URL validation only apply to v2+ snaps
package/llms.txt CHANGED
@@ -98,7 +98,8 @@ Top-level fields: `version` (required, `"1.0"` or `"2.0"`), `theme` (optional, `
98
98
  - `cells` (array, required): sparse list of `{ row, col, color?: PaletteColor, content?: string }`
99
99
  - `gap` (optional): `"none"` (0px) | `"sm"` (1px) | `"md"` (2px) | `"lg"` (4px). Default: `"sm"`
100
100
  - `rowHeight` (number, optional, 8–64): pixel height per row. Default: 28. Grid height = rows × rowHeight
101
- - `select` (optional): `"off"` | `"single"` | `"multiple"`. Default: `"off"`. Taps write to `inputs[name]`
101
+ - `select` (optional): `"off"` | `"single"` | `"multiple"`. Default: `"off"`. With `select: "off"`, bind `on.press` for press-to-act (each press writes `"row,col"` to `inputs[name]` and fires the action). With `"single"` / `"multiple"`, presses accumulate selection state and pair with a separate submit `button`; `on.press` is ignored.
102
+ - Events: `press` — fires on cell press, only when `select: "off"`; `inputs[name]` is set to `"row,col"` before the bound action runs
102
103
 
103
104
  ### Container Components
104
105
 
@@ -196,6 +197,26 @@ import { withTursoServerless, createInMemoryDataStore } from "@farcaster/snap-tu
196
197
  - `@farcaster/snap-hono` — Hono adapter (`registerSnapHandler`)
197
198
  - `@farcaster/snap-turso` — `withTursoServerless`, `DataStore` / `DataStoreValue`, in-memory and Turso helpers
198
199
 
200
+ ## Template Project Setup
201
+
202
+ The `template/` directory is the starting point for a snap. It's an ESM project
203
+ (`"type": "module"`) with `moduleResolution: "NodeNext"`.
204
+
205
+ **ESM import rule (CRITICAL)**: all local relative imports must include the `.js`
206
+ extension, even though the source files are `.ts`:
207
+
208
+ ```ts
209
+ // ✅ correct
210
+ import { foo } from "./foo.js";
211
+
212
+ // ❌ wrong — fails `pnpm build`; on deploy every route returns 500 FUNCTION_INVOCATION_FAILED
213
+ import { foo } from "./foo";
214
+ ```
215
+
216
+ `tsx` dev accepts bare imports, so the bug only surfaces on deploy. Always run
217
+ `pnpm build` (`tsc --noEmit`) before deploying — NodeNext makes this a build-time error.
218
+ Bare package imports (`hono`, `@farcaster/snap`, etc.) do not need an extension.
219
+
199
220
  ## Full Documentation
200
221
 
201
222
  https://docs.farcaster.xyz/snap
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farcaster/snap",
3
- "version": "2.0.3",
3
+ "version": "2.1.1",
4
4
  "description": "Farcaster Snaps 🫰",
5
5
  "repository": {
6
6
  "type": "git",
@@ -7,17 +7,21 @@ import { POST_GRID_TAP_KEY } from "@farcaster/snap";
7
7
  import { useSnapColors } from "../hooks/use-snap-colors";
8
8
 
9
9
  export function SnapCellGrid({
10
- element: { props },
10
+ element: { props, on },
11
+ emit,
11
12
  }: {
12
- element: { props: Record<string, unknown> };
13
+ element: { props: Record<string, unknown>; on?: Record<string, unknown> };
14
+ emit: (name: string) => void;
13
15
  }) {
14
16
  const { get, set } = useStateStore();
15
17
  const colors = useSnapColors();
16
18
  const cols = Number(props.cols ?? 2);
17
19
  const rows = Number(props.rows ?? 2);
18
20
  const select = String(props.select ?? "off");
19
- const interactive = select !== "off";
20
21
  const isMultiple = select === "multiple";
22
+ const isSelectable = select !== "off";
23
+ const hasPressAction = Boolean(on?.press);
24
+ const interactive = isSelectable || hasPressAction;
21
25
  const cells = Array.isArray(props.cells) ? props.cells : [];
22
26
  const gap = String(props.gap ?? "sm");
23
27
  const gapMap: Record<string, number> = { none: 0, sm: 1, md: 2, lg: 4 };
@@ -36,7 +40,8 @@ export function SnapCellGrid({
36
40
  }
37
41
  }
38
42
 
39
- const isSelected = (r: number, c: number) => selectedSet.has(`${r},${c}`);
43
+ const isSelected = (r: number, c: number) =>
44
+ isSelectable && selectedSet.has(`${r},${c}`);
40
45
 
41
46
  const handleTap = (r: number, c: number) => {
42
47
  const key = `${r},${c}`;
@@ -48,6 +53,7 @@ export function SnapCellGrid({
48
53
  } else {
49
54
  set(tapPath, key);
50
55
  }
56
+ if (hasPressAction) emit("press");
51
57
  };
52
58
 
53
59
  const cellMap = new Map<string, { color?: string; content?: string }>();
@@ -99,7 +105,7 @@ export function SnapCellGrid({
99
105
  }
100
106
  }
101
107
 
102
- const selectionLabel = interactive && selectedSet.size > 0
108
+ const selectionLabel = isSelectable && selectedSet.size > 0
103
109
  ? `inputs.${name}: ${[...selectedSet].join(isMultiple ? " | " : "")}`
104
110
  : null;
105
111
 
@@ -239,7 +239,11 @@ export function SnapViewCore({
239
239
  setPageKey((k) => k + 1);
240
240
  }, [spec]);
241
241
 
242
- const showConfetti = snap.effects?.includes("confetti");
242
+ const showConfetti = snap.effects?.includes("confetti") ?? false;
243
+ const [confettiKey, setConfettiKey] = useState(0);
244
+ useEffect(() => {
245
+ if (showConfetti) setConfettiKey((k) => k + 1);
246
+ }, [showConfetti, snap]);
243
247
 
244
248
  const accentName = snap.theme?.accent ?? "purple";
245
249
 
@@ -321,7 +325,7 @@ export function SnapViewCore({
321
325
 
322
326
  return (
323
327
  <div style={{ position: "relative", width: "100%" }}>
324
- {showConfetti && <ConfettiOverlay />}
328
+ {showConfetti && <ConfettiOverlay key={confettiKey} />}
325
329
  {loadingOverlay === undefined ? (
326
330
  <SnapLoadingOverlay
327
331
  appearance={appearance}
@@ -105,80 +105,86 @@ export function SnapCardV1({
105
105
  position: "relative",
106
106
  width: "100%",
107
107
  maxWidth,
108
- overflow: "hidden",
109
- ...(plain ? {} : {
110
- borderRadius: 16,
111
- border: `1px solid ${borderColor}`,
112
- backgroundColor: surfaceBg,
113
- }),
114
108
  }}
115
109
  >
116
110
  <div
117
- style={
118
- isClipped
119
- ? {
120
- maxHeight: SNAP_MAX_HEIGHT,
121
- overflow: "hidden",
122
- }
123
- : undefined
124
- }
111
+ style={{
112
+ position: "relative",
113
+ overflow: "hidden",
114
+ ...(plain ? {} : {
115
+ borderRadius: 16,
116
+ border: `1px solid ${borderColor}`,
117
+ backgroundColor: surfaceBg,
118
+ }),
119
+ }}
125
120
  >
126
- <div ref={contentRef} style={plain ? undefined : { padding: 16 }}>
127
- <SnapViewV1
128
- snap={snap}
129
- handlers={handlers}
130
- loading={loading}
121
+ <div
122
+ style={
123
+ isClipped
124
+ ? {
125
+ maxHeight: SNAP_MAX_HEIGHT,
126
+ overflow: "hidden",
127
+ }
128
+ : undefined
129
+ }
130
+ >
131
+ <div ref={contentRef} style={plain ? undefined : { padding: 16 }}>
132
+ <SnapViewV1
133
+ snap={snap}
134
+ handlers={handlers}
135
+ loading={loading}
136
+ appearance={appearance}
137
+ loadingOverlay={null}
138
+ />
139
+ </div>
140
+ </div>
141
+ {loadingOverlay === undefined ? (
142
+ <SnapLoadingOverlay
131
143
  appearance={appearance}
132
- loadingOverlay={null}
144
+ accentHex={accentHex}
145
+ active={loading}
133
146
  />
134
- </div>
147
+ ) : loading ? (
148
+ <>{loadingOverlay}</>
149
+ ) : null}
135
150
  </div>
136
- {loadingOverlay === undefined ? (
137
- <SnapLoadingOverlay
138
- appearance={appearance}
139
- accentHex={accentHex}
140
- active={loading}
141
- />
142
- ) : loading ? (
143
- <>{loadingOverlay}</>
144
- ) : null}
145
151
  {isExpandable ? (
146
- <div
152
+ <button
153
+ type="button"
154
+ aria-expanded={isExpanded}
155
+ onClick={() => setIsExpanded((value) => !value)}
147
156
  style={{
148
- display: "flex",
149
- justifyContent: "center",
150
- padding: plain ? "8px 0 0" : "10px 16px 12px",
151
- ...(plain
152
- ? {}
153
- : { borderTop: `1px solid ${borderColor}` }),
157
+ position: "absolute",
158
+ bottom: 0,
159
+ left: "50%",
160
+ transform: "translate(-50%, 50%)",
161
+ appearance: "none",
162
+ border: `1px solid ${borderColor}`,
163
+ borderRadius: 9999,
164
+ backgroundColor: isDark ? "rgba(30,30,30,0.6)" : "rgba(255,255,255,0.6)",
165
+ backdropFilter: "blur(12px) saturate(180%)",
166
+ WebkitBackdropFilter: "blur(12px) saturate(180%)",
167
+ color: toggleText,
168
+ padding: "2px 10px",
169
+ fontSize: 12,
170
+ lineHeight: "16px",
171
+ fontWeight: 600,
172
+ cursor: "pointer",
173
+ zIndex: 11,
174
+ }}
175
+ onMouseEnter={(event) => {
176
+ event.currentTarget.style.backgroundColor = isDark
177
+ ? "rgba(50,50,50,0.7)"
178
+ : "rgba(245,245,245,0.75)";
179
+ }}
180
+ onMouseLeave={(event) => {
181
+ event.currentTarget.style.backgroundColor = isDark
182
+ ? "rgba(30,30,30,0.6)"
183
+ : "rgba(255,255,255,0.6)";
154
184
  }}
155
185
  >
156
- <button
157
- type="button"
158
- aria-expanded={isExpanded}
159
- onClick={() => setIsExpanded((value) => !value)}
160
- style={{
161
- appearance: "none",
162
- border: "none",
163
- borderRadius: 9999,
164
- backgroundColor: toggleBg,
165
- color: toggleText,
166
- padding: "6px 10px",
167
- fontSize: 13,
168
- lineHeight: "18px",
169
- fontWeight: 600,
170
- cursor: "pointer",
171
- }}
172
- onMouseEnter={(event) => {
173
- event.currentTarget.style.backgroundColor = toggleBgHover;
174
- }}
175
- onMouseLeave={(event) => {
176
- event.currentTarget.style.backgroundColor = toggleBg;
177
- }}
178
- >
179
- {isExpanded ? "Show less" : "Show more"}
180
- </button>
181
- </div>
186
+ {isExpanded ? "Show less" : "Show more"}
187
+ </button>
182
188
  ) : null}
183
189
  {actionError && (
184
190
  <div