@farcaster/snap 2.0.3 → 2.1.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.
@@ -1,6 +1,6 @@
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";
@@ -54,28 +54,63 @@ function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWa
54
54
  const { colors, mode } = useSnapTheme();
55
55
  const accentHex = resolveAccentHex(snap.theme?.accent, mode);
56
56
  const [contentHeight, setContentHeight] = useState(0);
57
+ const [isExpanded, setIsExpanded] = useState(false);
58
+ useEffect(() => {
59
+ setIsExpanded(false);
60
+ setContentHeight(0);
61
+ }, [snap]);
62
+ const isExpandable = !showOverflowWarning && contentHeight > SNAP_MAX_HEIGHT + 1;
63
+ const isClipped = isExpandable && !isExpanded;
57
64
  const content = (_jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, loadingOverlay: null }));
58
65
  if (plain) {
59
- return (_jsxs(_Fragment, { children: [content, loading
66
+ return (_jsxs(_Fragment, { children: [_jsx(View, { style: isClipped ? { maxHeight: SNAP_MAX_HEIGHT, overflow: "hidden" } : undefined, children: _jsx(View, { collapsable: false, onLayout: (e) => {
67
+ const nextHeight = Math.round(e.nativeEvent.layout.height);
68
+ setContentHeight((current) => isClipped
69
+ ? Math.max(current, nextHeight)
70
+ : current === nextHeight
71
+ ? current
72
+ : nextHeight);
73
+ }, children: content }) }), loading
60
74
  ? loadingOverlay === undefined
61
75
  ? _jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex })
62
76
  : loadingOverlay
63
- : null] }));
77
+ : null, isExpandable ? (_jsx(View, { style: [cardStyles.expandRow, cardStyles.expandRowPlain], children: _jsx(Pressable, { style: ({ pressed }) => [
78
+ cardStyles.expandButton,
79
+ {
80
+ backgroundColor: pressed ? colors.mutedHover : colors.muted,
81
+ },
82
+ ], onPress: () => setIsExpanded((value) => !value), children: _jsx(Text, { style: [cardStyles.expandButtonText, { color: colors.text }], children: isExpanded ? "Show less" : "Show more" }) }) })) : null] }));
64
83
  }
65
84
  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: {
85
+ const isDark = mode === "dark";
86
+ const pillBg = isDark ? "rgba(40,40,40,0.92)" : "rgba(255,255,255,0.92)";
87
+ const pillBgPressed = isDark ? "rgba(60,60,60,0.95)" : "rgba(240,240,240,0.95)";
88
+ return (_jsxs(_Fragment, { children: [_jsxs(View, { style: { position: "relative" }, children: [_jsxs(View, { style: {
89
+ borderRadius,
90
+ borderWidth: 1,
91
+ borderColor: colors.border,
92
+ backgroundColor: colors.surface,
93
+ maxHeight: showOverflowWarning ? undefined : isClipped ? SNAP_MAX_HEIGHT : undefined,
94
+ overflow: "hidden",
95
+ minHeight: 120,
96
+ }, children: [_jsx(View, { collapsable: false, onLayout: (e) => {
97
+ const nextHeight = Math.round(e.nativeEvent.layout.height);
98
+ setContentHeight((current) => isClipped
99
+ ? Math.max(current, nextHeight)
100
+ : current === nextHeight
101
+ ? current
102
+ : nextHeight);
103
+ }, 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
104
+ ? loadingOverlay === undefined
105
+ ? _jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex })
106
+ : loadingOverlay
107
+ : null] }), isExpandable ? (_jsx(View, { pointerEvents: "box-none", style: cardStyles.expandFloat, children: _jsx(Pressable, { style: ({ pressed }) => [
108
+ cardStyles.expandButton,
109
+ {
110
+ backgroundColor: pressed ? pillBgPressed : pillBg,
111
+ borderColor: colors.border,
112
+ },
113
+ ], 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
114
  paddingHorizontal: 12,
80
115
  paddingVertical: 8,
81
116
  fontSize: 13,
@@ -92,6 +127,33 @@ const cardStyles = StyleSheet.create({
92
127
  card: { borderWidth: 1, minHeight: 120, overflow: "hidden" },
93
128
  body: { paddingHorizontal: 16, paddingVertical: 16 },
94
129
  actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
130
+ expandFloat: {
131
+ position: "absolute",
132
+ left: 0,
133
+ right: 0,
134
+ bottom: -14,
135
+ height: 28,
136
+ alignItems: "center",
137
+ justifyContent: "center",
138
+ },
139
+ expandRowPlain: {
140
+ paddingTop: 8,
141
+ alignItems: "center",
142
+ },
143
+ expandButton: {
144
+ minWidth: 92,
145
+ alignItems: "center",
146
+ justifyContent: "center",
147
+ borderRadius: 9999,
148
+ borderWidth: 1,
149
+ paddingHorizontal: 10,
150
+ paddingVertical: 4,
151
+ },
152
+ expandButtonText: {
153
+ fontSize: 12,
154
+ lineHeight: 16,
155
+ fontWeight: "600",
156
+ },
95
157
  warningOverlay: {
96
158
  position: "absolute",
97
159
  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.0",
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