Skip to content

Commit e4a984b

Browse files
authored
fix(snap): polish overlays and compact text spacing
1 parent 564b7e4 commit e4a984b

9 files changed

Lines changed: 118 additions & 55 deletions

File tree

examples/component-improvements/src/index.ts

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { registerSnapHandler } from "@farcaster/snap-hono";
44
import type { SnapElementInput, SnapHandlerResult } from "@farcaster/snap";
55

66
const app = new Hono();
7+
const SNAP_IMAGE_URL =
8+
"https://boey4mnqvpqdktto.public.blob.vercel-storage.com/chain/9d1648dc4d5a42fe.png";
79

810
registerSnapHandler(app, async (ctx): Promise<SnapHandlerResult> => {
911
const base = snapBaseUrlFromRequest(ctx.request);
@@ -19,7 +21,7 @@ export default app;
1921

2022
function componentImprovementsPage(
2123
base: string,
22-
submitted: boolean,
24+
submitted: boolean
2325
): SnapHandlerResult {
2426
return {
2527
version: "2.0",
@@ -30,14 +32,20 @@ function componentImprovementsPage(
3032
elements: {
3133
page: {
3234
type: "stack",
33-
children: ["banner", "showcase-controls", "pager", "actions", "status"],
35+
children: [
36+
"banner",
37+
"showcase-controls",
38+
"pager",
39+
"actions",
40+
"status",
41+
],
3442
},
3543
banner: {
3644
type: "image",
3745
props: {
38-
url: "https://placehold.co/1200x300/0f766e/ffffff.png?text=NEYN-11381",
46+
url: SNAP_IMAGE_URL,
3947
aspect: "4:1",
40-
alt: "Compact teal NEYN-11381 banner",
48+
alt: "Compact chain banner",
4149
title: "NEYN-11381 showcase",
4250
subtitle: "Paginator, image overlays, compact copy, dense grids",
4351
},
@@ -91,28 +99,32 @@ function componentImprovementsPage(
9199
"jump-medium-grid": paginatorButton("Medium grid", "paginator_go_to", {
92100
page: 4,
93101
}),
94-
overview: pageStack("overview-title", "overview-copy", "overview-short"),
102+
overview: pageStack(
103+
"overview-title",
104+
"overview-copy",
105+
"overview-short"
106+
),
95107
"overview-title": heading("Visible paginator"),
96108
"overview-copy": caption(
97109
"This example keeps the built-in controls and indicators visible while also using custom local actions outside the paginator.",
98-
2,
110+
2
99111
),
100112
"overview-short": caption(
101-
"Text now wraps by default; add maxLines only when a section needs a bounded height.",
113+
"Text now wraps by default; add maxLines only when a section needs a bounded height."
102114
),
103115
copy: pageStack("copy-title", "copy-one-line", "copy-two-lines"),
104116
"copy-title": heading("Optional text clamping"),
105117
"copy-one-line": caption(
106-
"No maxLines prop here: this long piece of supporting copy can wrap naturally instead of being clamped by default.",
118+
"No maxLines prop here: this long piece of supporting copy can wrap naturally instead of being clamped by default."
107119
),
108120
"copy-two-lines": caption(
109121
"This text opts into maxLines: 2, giving longer explanatory copy a little more room without letting the snap become tall.",
110-
2,
122+
2
111123
),
112124
grid: pageStack("grid-title", "grid-copy", "dense-grid", "grid-next"),
113125
"grid-title": heading("Dense 1:1 grid"),
114126
"grid-copy": caption(
115-
"Square cells keep board-like layouts compact and predictable.",
127+
"Square cells keep board-like layouts compact and predictable."
116128
),
117129
"dense-grid": {
118130
type: "cell_grid",
@@ -128,12 +140,12 @@ function componentImprovementsPage(
128140
"small-grid": pageStack(
129141
"small-grid-title",
130142
"small-grid-copy",
131-
"small-square-grid",
143+
"small-square-grid"
132144
),
133145
"small-grid-title": heading("Small centered grid"),
134146
"small-grid-copy": caption(
135147
"maxWidth: sm caps this board, centers it, and keeps every cell square.",
136-
2,
148+
2
137149
),
138150
"small-square-grid": {
139151
type: "cell_grid",
@@ -149,12 +161,12 @@ function componentImprovementsPage(
149161
"medium-grid": pageStack(
150162
"medium-grid-title",
151163
"medium-grid-copy",
152-
"medium-square-grid",
164+
"medium-square-grid"
153165
),
154166
"medium-grid-title": heading("Medium centered grid"),
155167
"medium-grid-copy": caption(
156168
"maxWidth: md is wider than sm but still centers itself; lg/default remains full-width.",
157-
2,
169+
2
158170
),
159171
"medium-square-grid": {
160172
type: "cell_grid",
@@ -170,12 +182,12 @@ function componentImprovementsPage(
170182
"custom-nav": pageStack(
171183
"custom-nav-title",
172184
"custom-nav-copy",
173-
"custom-nav-row",
185+
"custom-nav-row"
174186
),
175187
"custom-nav-title": heading("Custom local controls"),
176188
"custom-nav-copy": caption(
177189
"Buttons can move the snap's one paginator locally without POSTing.",
178-
2,
190+
2
179191
),
180192
"custom-nav-row": {
181193
type: "stack",
@@ -189,7 +201,7 @@ function componentImprovementsPage(
189201
"image-card-image": {
190202
type: "image",
191203
props: {
192-
url: "https://placehold.co/1200x300/115e59/ffffff.png?text=4%3A1+Overlay",
204+
url: SNAP_IMAGE_URL,
193205
aspect: "4:1",
194206
alt: "4:1 card with overlay text",
195207
title: "Image props, not hero",
@@ -200,13 +212,13 @@ function componentImprovementsPage(
200212
"many-pages-title": heading("More than six pages"),
201213
"many-pages-copy": caption(
202214
"Paginator pages can exceed the normal per-container child count while the global element cap still applies.",
203-
2,
215+
2
204216
),
205217
limits: pageStack("limits-title", "limits-copy", "limits-back"),
206218
"limits-title": heading("Validation stays strict"),
207219
"limits-copy": caption(
208220
"The local page index never becomes a POST input, and snap-level limits still protect the whole tree.",
209-
2,
221+
2
210222
),
211223
"limits-back": paginatorButton("Go to first", "paginator_go_to", {
212224
page: 0,
@@ -215,7 +227,7 @@ function componentImprovementsPage(
215227
"finish-title": heading("Compact actions"),
216228
"finish-copy": caption(
217229
"The shorter button height and restored gaps should feel compact without crowding the layout.",
218-
2,
230+
2
219231
),
220232
actions: {
221233
type: "stack",
@@ -286,7 +298,7 @@ function caption(content: string, maxLines?: number): SnapElementInput {
286298
function paginatorButton(
287299
label: string,
288300
action: "paginator_next" | "paginator_prev" | "paginator_go_to",
289-
params: Record<string, unknown> = {},
301+
params: Record<string, unknown> = {}
290302
): SnapElementInput {
291303
return {
292304
type: "button",

pkgs/snap/src/react-native/components/snap-image.tsx

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,19 @@ export function SnapImage({
3636
accessibilityLabel={alt || undefined}
3737
/>
3838
{hasOverlay ? (
39-
<View style={styles.overlay}>
40-
{title ? (
41-
<Text numberOfLines={1} style={styles.title}>
42-
{title}
43-
</Text>
44-
) : null}
45-
{subtitle ? (
46-
<Text numberOfLines={1} style={styles.subtitle}>
47-
{subtitle}
48-
</Text>
49-
) : null}
39+
<View style={styles.overlay} pointerEvents="none">
40+
<View style={styles.overlayContent}>
41+
{title ? (
42+
<Text numberOfLines={1} style={styles.title}>
43+
{title}
44+
</Text>
45+
) : null}
46+
{subtitle ? (
47+
<Text numberOfLines={1} style={styles.subtitle}>
48+
{subtitle}
49+
</Text>
50+
) : null}
51+
</View>
5052
</View>
5153
) : null}
5254
</View>
@@ -71,21 +73,33 @@ const styles = StyleSheet.create({
7173
left: 0,
7274
right: 0,
7375
bottom: 0,
74-
paddingHorizontal: 12,
75-
paddingTop: 24,
76-
paddingBottom: 10,
77-
backgroundColor: "rgba(0, 0, 0, 0.48)",
76+
paddingHorizontal: 3,
77+
paddingBottom: 3,
78+
},
79+
overlayContent: {
80+
alignSelf: "flex-start",
81+
maxWidth: "100%",
82+
borderRadius: 5,
83+
paddingHorizontal: 5,
84+
paddingVertical: 3,
85+
backgroundColor: "rgba(0, 0, 0, 0.22)",
7886
},
7987
title: {
8088
color: "#fff",
8189
fontSize: 14,
8290
lineHeight: 18,
8391
fontWeight: "700",
92+
textShadowColor: "#000",
93+
textShadowOffset: { width: 1.25, height: 1.25 },
94+
textShadowRadius: 1,
8495
},
8596
subtitle: {
86-
color: "rgba(255, 255, 255, 0.85)",
97+
color: "#fff",
8798
fontSize: 12,
8899
lineHeight: 16,
89100
fontWeight: "500",
101+
textShadowColor: "#000",
102+
textShadowOffset: { width: 1.25, height: 1.25 },
103+
textShadowRadius: 1,
90104
},
91105
});

pkgs/snap/src/react-native/components/snap-item-group.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ export function SnapItemGroup({
1818
const { colors } = useSnapTheme();
1919
const border = Boolean(props.border);
2020
const separator = Boolean(props.separator);
21-
const gap = GAP_MAP[String(props.gap ?? "sm")] ?? 4;
21+
const explicitGap =
22+
typeof props.gap === "string" ? String(props.gap) : undefined;
23+
const defaultGap = border || separator ? "sm" : "none";
24+
const gap = GAP_MAP[explicitGap ?? defaultGap] ?? GAP_MAP[defaultGap]!;
2225
const items = Children.toArray(children);
2326

2427
return (

pkgs/snap/src/react-native/components/snap-item.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ const styles = StyleSheet.create({
135135
borderRadius: 9999,
136136
},
137137
title: {
138-
fontSize: 15,
139-
lineHeight: 20,
138+
fontSize: 14,
139+
lineHeight: 19,
140140
fontWeight: "500",
141141
},
142142
description: {

pkgs/snap/src/react-native/components/snap-text.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@ import { StyleSheet, Text, View } from "react-native";
33
import { useSnapStackDirection } from "../stack-direction-context";
44
import { useSnapTheme } from "../theme";
55

6-
const SIZE_STYLES: Record<string, { fontSize: number; lineHeight?: number; fontWeight?: "400" | "500" | "600" | "700" }> = {
7-
md: { fontSize: 16, lineHeight: 22 },
6+
const SIZE_STYLES: Record<
7+
string,
8+
{
9+
fontSize: number;
10+
lineHeight?: number;
11+
fontWeight?: "400" | "500" | "600" | "700";
12+
}
13+
> = {
14+
md: { fontSize: 15, lineHeight: 21 },
815
sm: { fontSize: 13, lineHeight: 16 },
916
};
1017

@@ -20,13 +27,16 @@ export function SnapText({
2027
const content = String(props.content ?? "");
2128
const size = String(props.size ?? "md");
2229
const weight = props.weight ? String(props.weight) : undefined;
23-
const align = (props.align as "left" | "center" | "right" | undefined) ?? undefined;
30+
const align =
31+
(props.align as "left" | "center" | "right" | undefined) ?? undefined;
2432

2533
const sizeStyle = SIZE_STYLES[size] ?? SIZE_STYLES.md;
2634
const resolvedWeight = weight ? WEIGHT_MAP[weight] : sizeStyle?.fontWeight;
27-
const textAlign = align === "center" ? "center" : align === "right" ? "right" : "left";
35+
const textAlign =
36+
align === "center" ? "center" : align === "right" ? "right" : "left";
2837
const inHorizontalStack = useSnapStackDirection() === "horizontal";
29-
const maxLines = typeof props.maxLines === "number" ? props.maxLines : undefined;
38+
const maxLines =
39+
typeof props.maxLines === "number" ? props.maxLines : undefined;
3040

3141
return (
3242
<View style={inHorizontalStack ? styles.wrapRow : styles.wrapCol}>

pkgs/snap/src/react/components/image.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export function SnapImage({
2929
ratio={ratio}
3030
className={cn(
3131
"relative overflow-hidden rounded-lg",
32-
inHorizontalStack ? "min-w-0 flex-1 basis-0" : "w-full",
32+
inHorizontalStack ? "min-w-0 flex-1 basis-0" : "w-full"
3333
)}
3434
>
3535
{/* eslint-disable-next-line @next/next/no-img-element */}
@@ -39,14 +39,27 @@ export function SnapImage({
3939
className="absolute inset-0 size-full object-cover"
4040
/>
4141
{hasOverlay && (
42-
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/75 via-black/35 to-transparent p-3 pt-8 text-white">
42+
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 via-black/35 to-transparent p-3 pt-10 text-white">
4343
{title && (
44-
<div className="truncate text-sm font-semibold leading-5">
44+
<div
45+
className="truncate text-sm font-semibold leading-5"
46+
style={{
47+
textShadow:
48+
"0 1px 2px rgba(0,0,0,0.95), 0 0 3px rgba(0,0,0,0.9)",
49+
WebkitTextStroke: "0.25px rgba(0,0,0,0.75)",
50+
}}
51+
>
4552
{title}
4653
</div>
4754
)}
4855
{subtitle && (
49-
<div className="truncate text-xs font-medium leading-4 text-white/85">
56+
<div
57+
className="truncate text-xs font-medium leading-4 text-white/90"
58+
style={{
59+
textShadow:
60+
"0 1px 2px rgba(0,0,0,0.95), 0 0 3px rgba(0,0,0,0.9)",
61+
}}
62+
>
5063
{subtitle}
5164
</div>
5265
)}

pkgs/snap/src/react/components/item-group.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ export function SnapItemGroup({
2121
}) {
2222
const border = Boolean(props.border);
2323
const separator = Boolean(props.separator);
24-
const gap = GAP_MAP[String(props.gap ?? "sm")] ?? "gap-1";
24+
const explicitGap =
25+
typeof props.gap === "string" ? String(props.gap) : undefined;
26+
const defaultGap = border || separator ? "sm" : "none";
27+
const gap = GAP_MAP[explicitGap ?? defaultGap] ?? GAP_MAP[defaultGap]!;
2528
const items = Children.toArray(children);
2629
const colors = useSnapColors();
2730

pkgs/snap/src/react/components/item.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function SnapItem({
7373
"gap-2 py-1.5",
7474
inBorderedGroup ? "px-2" : "px-0",
7575
/** Horizontal: share width with peers. Vertical: don't fill column height. */
76-
inHorizontalStack && "flex-1",
76+
inHorizontalStack && "flex-1"
7777
)}
7878
style={{
7979
columnGap: 8,
@@ -111,7 +111,11 @@ export function SnapItem({
111111
</ItemMedia>
112112
)}
113113
<ItemContent className="gap-0">
114-
<ItemTitle style={{ color: colors.text }}>{title}</ItemTitle>
114+
<ItemTitle
115+
style={{ color: colors.text, fontSize: 14, lineHeight: "19px" }}
116+
>
117+
{title}
118+
</ItemTitle>
115119
{description && (
116120
<ItemDescription
117121
className="mt-0 text-xs leading-snug"

0 commit comments

Comments
 (0)