@farcaster/snap 2.6.1 → 2.6.3

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.
@@ -18,5 +18,10 @@ export function SnapImage({ element: { props }, }) {
18
18
  const ratio = aspectToRatio(String(props.aspect ?? "1:1"));
19
19
  const stackDir = useSnapStackDirection();
20
20
  const inHorizontalStack = stackDir === "horizontal";
21
- return (_jsxs(AspectRatio, { ratio: ratio, className: cn("relative overflow-hidden rounded-lg", inHorizontalStack ? "min-w-0 flex-1 basis-0" : "w-full"), children: [_jsx("img", { src: url, alt: alt, className: "absolute inset-0 size-full object-cover" }), hasOverlay && (_jsxs("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", children: [title && (_jsx("div", { className: "truncate text-sm font-semibold leading-5", children: title })), subtitle && (_jsx("div", { className: "truncate text-xs font-medium leading-4 text-white/85", children: subtitle }))] }))] }));
21
+ return (_jsxs(AspectRatio, { ratio: ratio, className: cn("relative overflow-hidden rounded-lg", inHorizontalStack ? "min-w-0 flex-1 basis-0" : "w-full"), children: [_jsx("img", { src: url, alt: alt, className: "absolute inset-0 size-full object-cover" }), hasOverlay && (_jsxs("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", children: [title && (_jsx("div", { className: "truncate text-sm font-semibold leading-5", style: {
22
+ textShadow: "0 1px 2px rgba(0,0,0,0.95), 0 0 3px rgba(0,0,0,0.9)",
23
+ WebkitTextStroke: "0.25px rgba(0,0,0,0.75)",
24
+ }, children: title })), subtitle && (_jsx("div", { className: "truncate text-xs font-medium leading-4 text-white/90", style: {
25
+ textShadow: "0 1px 2px rgba(0,0,0,0.95), 0 0 3px rgba(0,0,0,0.9)",
26
+ }, children: subtitle }))] }))] }));
22
27
  }
@@ -13,7 +13,9 @@ const GAP_MAP = {
13
13
  export function SnapItemGroup({ element: { props }, children, }) {
14
14
  const border = Boolean(props.border);
15
15
  const separator = Boolean(props.separator);
16
- const gap = GAP_MAP[String(props.gap ?? "sm")] ?? "gap-1";
16
+ const explicitGap = typeof props.gap === "string" ? String(props.gap) : undefined;
17
+ const defaultGap = border || separator ? "sm" : "none";
18
+ const gap = GAP_MAP[explicitGap ?? defaultGap] ?? GAP_MAP[defaultGap];
17
19
  const items = Children.toArray(children);
18
20
  const colors = useSnapColors();
19
21
  return (_jsx(SnapItemGroupBorderProvider, { value: border, children: _jsx("div", { className: cn("flex flex-col", border && "rounded-lg border", gap), style: border ? { borderColor: colors.border } : undefined, children: items.map((child, i) => (_jsxs(Fragment, { children: [separator && i > 0 && (_jsx("div", { className: "h-px", style: { backgroundColor: colors.border } })), child] }, i))) }) }));
@@ -44,7 +44,7 @@ export function SnapItem({ element: { props, children: childIds }, children, })
44
44
  alignSelf: "center",
45
45
  borderRadius: media.round ? "9999px" : undefined,
46
46
  transform: "none",
47
- }, children: _jsx("img", { src: media.url, alt: media.alt ?? "", className: "size-full object-cover" }) })), _jsxs(ItemContent, { className: "gap-0", children: [_jsx(ItemTitle, { style: { color: colors.text }, children: title }), description && (_jsx(ItemDescription, { className: "mt-0 text-xs leading-snug", style: {
47
+ }, children: _jsx("img", { src: media.url, alt: media.alt ?? "", className: "size-full object-cover" }) })), _jsxs(ItemContent, { className: "gap-0", children: [_jsx(ItemTitle, { style: { color: colors.text, fontSize: 14, lineHeight: "19px" }, children: title }), description && (_jsx(ItemDescription, { className: "mt-0 text-xs leading-snug", style: {
48
48
  color: colors.textMuted,
49
49
  fontSize: 12,
50
50
  lineHeight: "16px",
@@ -11,7 +11,9 @@ const SIZE_MAP = {
11
11
  export function SnapText({ element: { props }, }) {
12
12
  const content = String(props.content ?? "");
13
13
  const size = String(props.size ?? "md");
14
- const weight = props.weight ? String(props.weight) : undefined;
14
+ const weight = props.weight
15
+ ? String(props.weight)
16
+ : undefined;
15
17
  const align = props.align ?? undefined;
16
18
  const config = SIZE_MAP[size] ?? SIZE_MAP.md;
17
19
  const colors = useSnapColors();
@@ -29,6 +31,7 @@ export function SnapText({ element: { props }, }) {
29
31
  */
30
32
  inHorizontalStack ? "min-w-0 shrink" : "min-w-0"), style: {
31
33
  color: colors.text,
34
+ fontSize: size === "md" ? 15 : undefined,
32
35
  lineHeight: size === "sm" ? 1.35 : 1.4,
33
36
  ...(maxLines
34
37
  ? {
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Image } from "expo-image";
3
+ import { useCallback, useState } from "react";
3
4
  import { StyleSheet, Text, View } from "react-native";
4
5
  import { useSnapStackDirection } from "../stack-direction-context.js";
5
6
  function aspectToRatio(aspect) {
@@ -17,11 +18,17 @@ export function SnapImage({ element: { props }, }) {
17
18
  const ratio = aspectToRatio(String(props.aspect ?? "1:1"));
18
19
  const stackDir = useSnapStackDirection();
19
20
  const inHorizontalStack = stackDir === "horizontal";
20
- return (_jsxs(View, { style: [
21
+ const [frameWidth, setFrameWidth] = useState(0);
22
+ const measuredHeight = frameWidth > 0 ? frameWidth / ratio : undefined;
23
+ const handleLayout = useCallback((event) => {
24
+ const nextWidth = Math.round(event.nativeEvent.layout.width);
25
+ setFrameWidth((currentWidth) => currentWidth !== nextWidth ? nextWidth : currentWidth);
26
+ }, []);
27
+ return (_jsxs(View, { onLayout: handleLayout, style: [
21
28
  styles.frame,
22
29
  inHorizontalStack ? styles.frameInHorizontalRow : styles.frameFullWidth,
23
- { aspectRatio: ratio },
24
- ], children: [_jsx(Image, { source: { uri: url }, style: StyleSheet.absoluteFill, contentFit: "cover", accessibilityLabel: alt || undefined }), hasOverlay ? (_jsxs(View, { style: styles.overlay, children: [title ? (_jsx(Text, { numberOfLines: 1, style: styles.title, children: title })) : null, subtitle ? (_jsx(Text, { numberOfLines: 1, style: styles.subtitle, children: subtitle })) : null] })) : null] }));
30
+ measuredHeight ? { height: measuredHeight } : { aspectRatio: ratio },
31
+ ], children: [_jsx(Image, { source: { uri: url }, style: StyleSheet.absoluteFill, contentFit: "cover", accessibilityLabel: alt || undefined }), hasOverlay ? (_jsx(View, { style: styles.overlay, pointerEvents: "none", children: _jsxs(View, { style: styles.overlayContent, children: [title ? (_jsx(Text, { numberOfLines: 1, style: styles.title, children: title })) : null, subtitle ? (_jsx(Text, { numberOfLines: 1, style: styles.subtitle, children: subtitle })) : null] }) })) : null] }));
25
32
  }
26
33
  const styles = StyleSheet.create({
27
34
  frame: {
@@ -41,21 +48,33 @@ const styles = StyleSheet.create({
41
48
  left: 0,
42
49
  right: 0,
43
50
  bottom: 0,
44
- paddingHorizontal: 12,
45
- paddingTop: 24,
46
- paddingBottom: 10,
47
- backgroundColor: "rgba(0, 0, 0, 0.48)",
51
+ paddingHorizontal: 3,
52
+ paddingBottom: 3,
53
+ },
54
+ overlayContent: {
55
+ alignSelf: "flex-start",
56
+ maxWidth: "100%",
57
+ borderRadius: 5,
58
+ paddingHorizontal: 5,
59
+ paddingVertical: 3,
60
+ backgroundColor: "rgba(0, 0, 0, 0.22)",
48
61
  },
49
62
  title: {
50
63
  color: "#fff",
51
64
  fontSize: 14,
52
65
  lineHeight: 18,
53
66
  fontWeight: "700",
67
+ textShadowColor: "#000",
68
+ textShadowOffset: { width: 1.25, height: 1.25 },
69
+ textShadowRadius: 1,
54
70
  },
55
71
  subtitle: {
56
- color: "rgba(255, 255, 255, 0.85)",
72
+ color: "#fff",
57
73
  fontSize: 12,
58
74
  lineHeight: 16,
59
75
  fontWeight: "500",
76
+ textShadowColor: "#000",
77
+ textShadowOffset: { width: 1.25, height: 1.25 },
78
+ textShadowRadius: 1,
60
79
  },
61
80
  });
@@ -13,7 +13,9 @@ export function SnapItemGroup({ element: { props }, children, }) {
13
13
  const { colors } = useSnapTheme();
14
14
  const border = Boolean(props.border);
15
15
  const separator = Boolean(props.separator);
16
- const gap = GAP_MAP[String(props.gap ?? "sm")] ?? 4;
16
+ const explicitGap = typeof props.gap === "string" ? String(props.gap) : undefined;
17
+ const defaultGap = border || separator ? "sm" : "none";
18
+ const gap = GAP_MAP[explicitGap ?? defaultGap] ?? GAP_MAP[defaultGap];
17
19
  const items = Children.toArray(children);
18
20
  return (_jsx(SnapItemGroupBorderProvider, { value: border, children: _jsx(View, { style: [
19
21
  styles.group,
@@ -74,8 +74,8 @@ const styles = StyleSheet.create({
74
74
  borderRadius: 9999,
75
75
  },
76
76
  title: {
77
- fontSize: 15,
78
- lineHeight: 20,
77
+ fontSize: 14,
78
+ lineHeight: 19,
79
79
  fontWeight: "500",
80
80
  },
81
81
  description: {
@@ -3,7 +3,7 @@ import { StyleSheet, Text, View } from "react-native";
3
3
  import { useSnapStackDirection } from "../stack-direction-context.js";
4
4
  import { useSnapTheme } from "../theme.js";
5
5
  const SIZE_STYLES = {
6
- md: { fontSize: 16, lineHeight: 22 },
6
+ md: { fontSize: 15, lineHeight: 21 },
7
7
  sm: { fontSize: 13, lineHeight: 16 },
8
8
  };
9
9
  const WEIGHT_MAP = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farcaster/snap",
3
- "version": "2.6.1",
3
+ "version": "2.6.3",
4
4
  "description": "Farcaster Snaps 🫰",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,7 +29,7 @@ export function SnapImage({
29
29
  ratio={ratio}
30
30
  className={cn(
31
31
  "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"
33
33
  )}
34
34
  >
35
35
  {/* eslint-disable-next-line @next/next/no-img-element */}
@@ -39,14 +39,27 @@ export function SnapImage({
39
39
  className="absolute inset-0 size-full object-cover"
40
40
  />
41
41
  {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">
43
43
  {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
+ >
45
52
  {title}
46
53
  </div>
47
54
  )}
48
55
  {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
+ >
50
63
  {subtitle}
51
64
  </div>
52
65
  )}
@@ -21,7 +21,10 @@ export function SnapItemGroup({
21
21
  }) {
22
22
  const border = Boolean(props.border);
23
23
  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]!;
25
28
  const items = Children.toArray(children);
26
29
  const colors = useSnapColors();
27
30
 
@@ -73,7 +73,7 @@ export function SnapItem({
73
73
  "gap-2 py-1.5",
74
74
  inBorderedGroup ? "px-2" : "px-0",
75
75
  /** Horizontal: share width with peers. Vertical: don't fill column height. */
76
- inHorizontalStack && "flex-1",
76
+ inHorizontalStack && "flex-1"
77
77
  )}
78
78
  style={{
79
79
  columnGap: 8,
@@ -111,7 +111,11 @@ export function SnapItem({
111
111
  </ItemMedia>
112
112
  )}
113
113
  <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>
115
119
  {description && (
116
120
  <ItemDescription
117
121
  className="mt-0 text-xs leading-snug"
@@ -17,13 +17,16 @@ export function SnapText({
17
17
  }) {
18
18
  const content = String(props.content ?? "");
19
19
  const size = String(props.size ?? "md") as "md" | "sm";
20
- const weight = props.weight ? String(props.weight) as "bold" | "normal" : undefined;
20
+ const weight = props.weight
21
+ ? (String(props.weight) as "bold" | "normal")
22
+ : undefined;
21
23
  const align = (props.align as "left" | "center" | "right") ?? undefined;
22
24
  const config = SIZE_MAP[size] ?? SIZE_MAP.md;
23
25
  const colors = useSnapColors();
24
26
  const stackDir = useSnapStackDirection();
25
27
  const inHorizontalStack = stackDir === "horizontal";
26
- const maxLines = typeof props.maxLines === "number" ? props.maxLines : undefined;
28
+ const maxLines =
29
+ typeof props.maxLines === "number" ? props.maxLines : undefined;
27
30
 
28
31
  return (
29
32
  <Text
@@ -39,10 +42,11 @@ export function SnapText({
39
42
  * column's height, distributing siblings when the row is taller than its
40
43
  * content (e.g. text next to a tall image).
41
44
  */
42
- inHorizontalStack ? "min-w-0 shrink" : "min-w-0",
45
+ inHorizontalStack ? "min-w-0 shrink" : "min-w-0"
43
46
  )}
44
47
  style={{
45
48
  color: colors.text,
49
+ fontSize: size === "md" ? 15 : undefined,
46
50
  lineHeight: size === "sm" ? 1.35 : 1.4,
47
51
  ...(maxLines
48
52
  ? {
@@ -1,5 +1,7 @@
1
1
  import type { ComponentRenderProps } from "@json-render/react-native";
2
2
  import { Image } from "expo-image";
3
+ import { useCallback, useState } from "react";
4
+ import type { ViewProps } from "react-native";
3
5
  import { StyleSheet, Text, View } from "react-native";
4
6
  import { useSnapStackDirection } from "../stack-direction-context";
5
7
 
@@ -20,13 +22,22 @@ export function SnapImage({
20
22
  const ratio = aspectToRatio(String(props.aspect ?? "1:1"));
21
23
  const stackDir = useSnapStackDirection();
22
24
  const inHorizontalStack = stackDir === "horizontal";
25
+ const [frameWidth, setFrameWidth] = useState(0);
26
+ const measuredHeight = frameWidth > 0 ? frameWidth / ratio : undefined;
27
+ const handleLayout = useCallback<NonNullable<ViewProps["onLayout"]>>((event) => {
28
+ const nextWidth = Math.round(event.nativeEvent.layout.width);
29
+ setFrameWidth((currentWidth) =>
30
+ currentWidth !== nextWidth ? nextWidth : currentWidth,
31
+ );
32
+ }, []);
23
33
 
24
34
  return (
25
35
  <View
36
+ onLayout={handleLayout}
26
37
  style={[
27
38
  styles.frame,
28
39
  inHorizontalStack ? styles.frameInHorizontalRow : styles.frameFullWidth,
29
- { aspectRatio: ratio },
40
+ measuredHeight ? { height: measuredHeight } : { aspectRatio: ratio },
30
41
  ]}
31
42
  >
32
43
  <Image
@@ -36,17 +47,19 @@ export function SnapImage({
36
47
  accessibilityLabel={alt || undefined}
37
48
  />
38
49
  {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}
50
+ <View style={styles.overlay} pointerEvents="none">
51
+ <View style={styles.overlayContent}>
52
+ {title ? (
53
+ <Text numberOfLines={1} style={styles.title}>
54
+ {title}
55
+ </Text>
56
+ ) : null}
57
+ {subtitle ? (
58
+ <Text numberOfLines={1} style={styles.subtitle}>
59
+ {subtitle}
60
+ </Text>
61
+ ) : null}
62
+ </View>
50
63
  </View>
51
64
  ) : null}
52
65
  </View>
@@ -71,21 +84,33 @@ const styles = StyleSheet.create({
71
84
  left: 0,
72
85
  right: 0,
73
86
  bottom: 0,
74
- paddingHorizontal: 12,
75
- paddingTop: 24,
76
- paddingBottom: 10,
77
- backgroundColor: "rgba(0, 0, 0, 0.48)",
87
+ paddingHorizontal: 3,
88
+ paddingBottom: 3,
89
+ },
90
+ overlayContent: {
91
+ alignSelf: "flex-start",
92
+ maxWidth: "100%",
93
+ borderRadius: 5,
94
+ paddingHorizontal: 5,
95
+ paddingVertical: 3,
96
+ backgroundColor: "rgba(0, 0, 0, 0.22)",
78
97
  },
79
98
  title: {
80
99
  color: "#fff",
81
100
  fontSize: 14,
82
101
  lineHeight: 18,
83
102
  fontWeight: "700",
103
+ textShadowColor: "#000",
104
+ textShadowOffset: { width: 1.25, height: 1.25 },
105
+ textShadowRadius: 1,
84
106
  },
85
107
  subtitle: {
86
- color: "rgba(255, 255, 255, 0.85)",
108
+ color: "#fff",
87
109
  fontSize: 12,
88
110
  lineHeight: 16,
89
111
  fontWeight: "500",
112
+ textShadowColor: "#000",
113
+ textShadowOffset: { width: 1.25, height: 1.25 },
114
+ textShadowRadius: 1,
90
115
  },
91
116
  });
@@ -18,7 +18,10 @@ export function SnapItemGroup({
18
18
  const { colors } = useSnapTheme();
19
19
  const border = Boolean(props.border);
20
20
  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]!;
22
25
  const items = Children.toArray(children);
23
26
 
24
27
  return (
@@ -135,8 +135,8 @@ const styles = StyleSheet.create({
135
135
  borderRadius: 9999,
136
136
  },
137
137
  title: {
138
- fontSize: 15,
139
- lineHeight: 20,
138
+ fontSize: 14,
139
+ lineHeight: 19,
140
140
  fontWeight: "500",
141
141
  },
142
142
  description: {
@@ -3,8 +3,15 @@ import { StyleSheet, Text, View } from "react-native";
3
3
  import { useSnapStackDirection } from "../stack-direction-context";
4
4
  import { useSnapTheme } from "../theme";
5
5
 
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 },
8
15
  sm: { fontSize: 13, lineHeight: 16 },
9
16
  };
10
17
 
@@ -20,13 +27,16 @@ export function SnapText({
20
27
  const content = String(props.content ?? "");
21
28
  const size = String(props.size ?? "md");
22
29
  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;
24
32
 
25
33
  const sizeStyle = SIZE_STYLES[size] ?? SIZE_STYLES.md;
26
34
  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";
28
37
  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;
30
40
 
31
41
  return (
32
42
  <View style={inHorizontalStack ? styles.wrapRow : styles.wrapCol}>