convex-feedback-ui 0.2.0-beta.3 → 0.2.0-beta.4

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.
Files changed (62) hide show
  1. package/README.md +3 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/native/expo/FeedbackRoutes.d.ts.map +1 -1
  4. package/dist/native/expo/FeedbackRoutes.js +15 -2
  5. package/dist/native/expo/FeedbackRoutes.js.map +1 -1
  6. package/dist/native/expo/RoadmapRoutes.d.ts.map +1 -1
  7. package/dist/native/expo/RoadmapRoutes.js +22 -5
  8. package/dist/native/expo/RoadmapRoutes.js.map +1 -1
  9. package/dist/native/expo/RoadmapScreen.d.ts +1 -1
  10. package/dist/native/expo/RoadmapScreen.d.ts.map +1 -1
  11. package/dist/native/expo/RoadmapScreen.js +19 -12
  12. package/dist/native/expo/RoadmapScreen.js.map +1 -1
  13. package/dist/native/expo/RoadmapStackLayout.d.ts.map +1 -1
  14. package/dist/native/expo/RoadmapStackLayout.js +0 -1
  15. package/dist/native/expo/RoadmapStackLayout.js.map +1 -1
  16. package/dist/native/expo/index.d.ts +2 -1
  17. package/dist/native/expo/index.d.ts.map +1 -1
  18. package/dist/native/expo/index.js +2 -1
  19. package/dist/native/expo/index.js.map +1 -1
  20. package/dist/native/native/index.d.ts +1 -0
  21. package/dist/native/native/index.d.ts.map +1 -1
  22. package/dist/native/native/index.js +1 -0
  23. package/dist/native/native/index.js.map +1 -1
  24. package/dist/native/shared/ui/EntryCard.d.ts +1 -1
  25. package/dist/native/shared/ui/EntryCard.d.ts.map +1 -1
  26. package/dist/native/shared/ui/EntryCard.js +2 -2
  27. package/dist/native/shared/ui/EntryCard.js.map +1 -1
  28. package/dist/native/shared/ui/RoadmapBoard.d.ts +55 -0
  29. package/dist/native/shared/ui/RoadmapBoard.d.ts.map +1 -0
  30. package/dist/native/shared/ui/RoadmapBoard.js +89 -0
  31. package/dist/native/shared/ui/RoadmapBoard.js.map +1 -0
  32. package/dist/native/shared/ui/RoadmapScreen.d.ts +16 -1
  33. package/dist/native/shared/ui/RoadmapScreen.d.ts.map +1 -1
  34. package/dist/native/shared/ui/RoadmapScreen.js +26 -112
  35. package/dist/native/shared/ui/RoadmapScreen.js.map +1 -1
  36. package/dist/react/roadmap.js +3 -3
  37. package/dist/react/roadmap.js.map +1 -1
  38. package/dist/react/styles.css +6 -0
  39. package/dist/shared/messages.d.ts.map +1 -1
  40. package/dist/shared/messages.js +4 -3
  41. package/dist/shared/messages.js.map +1 -1
  42. package/dist/shared/types/messages.d.ts +4 -2
  43. package/dist/shared/types/messages.d.ts.map +1 -1
  44. package/dist/shared/types/screen.d.ts +2 -0
  45. package/dist/shared/types/screen.d.ts.map +1 -1
  46. package/package.json +2 -2
  47. package/src/native/expo/FeedbackRoutes.tsx +30 -7
  48. package/src/native/expo/README.md +6 -2
  49. package/src/native/expo/RoadmapRoutes.tsx +45 -7
  50. package/src/native/expo/RoadmapScreen.tsx +93 -17
  51. package/src/native/expo/RoadmapStackLayout.tsx +0 -1
  52. package/src/native/expo/index.ts +2 -0
  53. package/src/native/native/README.md +5 -0
  54. package/src/native/native/index.ts +1 -0
  55. package/src/native/shared/ui/EntryCard.tsx +2 -1
  56. package/src/native/shared/ui/RoadmapBoard.tsx +225 -0
  57. package/src/native/shared/ui/RoadmapScreen.tsx +94 -243
  58. package/src/react/roadmap.tsx +1 -6
  59. package/src/react/styles.css +6 -0
  60. package/src/shared/messages.ts +4 -3
  61. package/src/shared/types/messages.ts +5 -2
  62. package/src/shared/types/screen.ts +2 -0
@@ -1,5 +1,7 @@
1
1
  import { Stack, useLocalSearchParams, useRouter } from "expo-router";
2
+ import { useRef, useState } from "react";
2
3
  import { ActivityIndicator } from "react-native";
4
+ import type { SearchBarCommands } from "react-native-screens";
3
5
  import { useSafeAreaInsets } from "react-native-safe-area-context";
4
6
 
5
7
  import { useFeedbackBody } from "../../shared/context/FeedbackBodyProvider.js";
@@ -20,21 +22,50 @@ export function RoadmapBoardScreen() {
20
22
  const { messages, theme } = useFeedbackUi();
21
23
  const { routes, colors, androidToolbarIcons, pageSize } = useRoutedRoadmap();
22
24
  const router = useRouter();
25
+ const [query, setQuery] = useState("");
26
+ const searchRef = useRef<SearchBarCommands>(null);
23
27
 
24
28
  return (
25
29
  <>
30
+ <Stack.Screen
31
+ options={{
32
+ headerTitle: messages.roadmap.title,
33
+ headerBackVisible: false,
34
+ }}
35
+ />
26
36
  <FeedbackBoard.Root {...colors}>
27
37
  <RoadmapBoardContent
28
38
  pageSize={pageSize}
29
- onItemOpen={(item) =>
39
+ query={query}
40
+ onQueryChange={setQuery}
41
+ showHeader={false}
42
+ onItemOpen={(item) => {
43
+ searchRef.current?.blur();
30
44
  router.push(
31
45
  roadmapRouteHref(routes.item, roadmapRouteParams(item)),
32
46
  { relativeToDirectory: true },
33
- )
34
- }
47
+ );
48
+ }}
35
49
  />
36
50
  </FeedbackBoard.Root>
37
51
 
52
+ <Stack.SearchBar
53
+ ref={searchRef}
54
+ placeholder={messages.roadmap.searchPlaceholder}
55
+ onChangeText={(event) =>
56
+ setQuery(
57
+ (event as unknown as { nativeEvent: { text: string } }).nativeEvent
58
+ .text,
59
+ )
60
+ }
61
+ obscureBackground={false}
62
+ allowToolbarIntegration
63
+ hideNavigationBar={false}
64
+ textColor={theme.colors.text}
65
+ />
66
+ <Stack.Toolbar placement="bottom">
67
+ <Stack.Toolbar.SearchBarSlot />
68
+ </Stack.Toolbar>
38
69
  <Stack.Toolbar placement="left">
39
70
  <Stack.Toolbar.Button
40
71
  hidden={!router.canGoBack()}
@@ -61,7 +92,7 @@ export function RoadmapItemScreen() {
61
92
  }>();
62
93
  const { hooks } = useFeedbackBody();
63
94
  const { messages, theme } = useFeedbackUi();
64
- const { colors, androidToolbarIcons, entryPageSize, onEntryOpen } =
95
+ const { routes, colors, androidToolbarIcons, entryPageSize, onEntryOpen } =
65
96
  useRoutedRoadmap();
66
97
  const router = useRouter();
67
98
  const insets = useSafeAreaInsets();
@@ -84,7 +115,9 @@ export function RoadmapItemScreen() {
84
115
  <>
85
116
  {item ? (
86
117
  <>
87
- <Stack.Screen options={{ headerTitle: item.title }} />
118
+ <Stack.Screen
119
+ options={{ headerTitle: item.title, headerBackVisible: false }}
120
+ />
88
121
  <FeedbackBoard.Root {...colors}>
89
122
  <FeedbackBoard.List
90
123
  style={{
@@ -115,14 +148,19 @@ export function RoadmapItemScreen() {
115
148
 
116
149
  <Stack.Toolbar placement="left">
117
150
  <Stack.Toolbar.Button
118
- hidden={!router.canGoBack()}
119
151
  icon={
120
152
  process.env.EXPO_OS === "ios"
121
153
  ? "chevron.backward"
122
154
  : androidToolbarIcons.back
123
155
  }
124
156
  accessibilityLabel={messages.entry.back}
125
- onPress={() => router.back()}
157
+ onPress={() => {
158
+ if (router.canGoBack()) {
159
+ router.back();
160
+ } else {
161
+ router.replace(roadmapRouteHref(routes.board));
162
+ }
163
+ }}
126
164
  tintColor={theme.colors.text}
127
165
  >
128
166
  {messages.entry.back}
@@ -1,9 +1,16 @@
1
1
  import { Stack, useRouter } from "expo-router";
2
+ import { useRef, useState } from "react";
3
+ import type { SearchBarCommands } from "react-native-screens";
2
4
 
3
- import { mergeFeedbackMessages } from "../../shared/messages.js";
4
- import { mergeFeedbackTheme } from "../../shared/theme.js";
5
- import { RoadmapScreen as NativeRoadmapScreen } from "../shared/ui/RoadmapScreen.js";
5
+ import { useFeedbackUi } from "../../shared/context/FeedbackProvider.js";
6
+ import type { mergeFeedbackMessages } from "../../shared/messages.js";
7
+ import type { mergeFeedbackTheme } from "../../shared/theme.js";
6
8
  import type { RoadmapScreenProps } from "../../shared/types/index.js";
9
+ import {
10
+ RoadmapProvider,
11
+ RoadmapScreen as NativeRoadmapScreen,
12
+ RoadmapScreenContent,
13
+ } from "../shared/ui/RoadmapScreen.js";
7
14
  import type {
8
15
  RoadmapAndroidToolbarIcons,
9
16
  RoadmapStackScreenOptions,
@@ -32,16 +39,52 @@ export type ExpoRoadmapScreenProps = RoadmapScreenProps &
32
39
  * `RoadmapStackLayout` when the board and item detail should be separate
33
40
  * discovered Expo Router pages.
34
41
  */
35
- export function RoadmapScreen({
36
- useStack = true,
37
- StackOptions,
38
- androidToolbarIcons = {},
39
- ...props
40
- }: ExpoRoadmapScreenProps) {
41
- if (!useStack) return <NativeRoadmapScreen {...props} />;
42
+ export function RoadmapScreen(props: ExpoRoadmapScreenProps) {
43
+ if (props.useStack === false) {
44
+ return <NativeRoadmapScreen {...props} />;
45
+ }
42
46
 
43
- const messages = mergeFeedbackMessages(props.messages);
44
- const theme = mergeFeedbackTheme(props.theme);
47
+ return (
48
+ <RoadmapProvider
49
+ hooks={props.hooks}
50
+ messages={props.messages}
51
+ theme={props.theme}
52
+ unstyled={props.unstyled}
53
+ commentSort={props.commentSort}
54
+ maxCommentDepth={props.maxCommentDepth}
55
+ transformComments={props.transformComments}
56
+ renderActor={props.renderActor}
57
+ onUnauthenticated={props.onUnauthenticated}
58
+ >
59
+ <StackedRoadmapScreen props={props} />
60
+ </RoadmapProvider>
61
+ );
62
+ }
63
+
64
+ function StackedRoadmapScreen({
65
+ props,
66
+ }: {
67
+ props: RoadmapScreenWithStack & RoadmapScreenProps;
68
+ }) {
69
+ const {
70
+ androidToolbarIcons = {},
71
+ hooks,
72
+ pageSize,
73
+ entryPageSize,
74
+ onEntryOpen,
75
+ onUnauthenticated,
76
+ primaryColor,
77
+ primaryForeground,
78
+ backgroundColor,
79
+ surfaceColor,
80
+ textColor,
81
+ mutedColor,
82
+ borderColor,
83
+ dangerColor,
84
+ } = props;
85
+ const { messages, theme } = useFeedbackUi();
86
+ const [query, setQuery] = useState("");
87
+ const searchRef = useRef<SearchBarCommands>(null);
45
88
 
46
89
  return (
47
90
  <>
@@ -49,23 +92,56 @@ export function RoadmapScreen({
49
92
  options={{
50
93
  headerTitle: messages.roadmap.title,
51
94
  headerShown: true,
52
- headerTransparent: true,
95
+ headerTransparent: false,
53
96
  headerShadowVisible: true,
54
97
  headerBackButtonDisplayMode: "minimal",
55
98
  headerBackVisible: false,
56
- headerTintColor: props.textColor ?? theme.colors.text,
99
+ headerTintColor: textColor ?? theme.colors.text,
57
100
  contentStyle: {
58
- backgroundColor: props.backgroundColor ?? theme.colors.background,
101
+ backgroundColor: backgroundColor ?? theme.colors.background,
59
102
  },
60
- ...StackOptions,
103
+ ...props.StackOptions,
61
104
  }}
62
105
  />
106
+ <Stack.SearchBar
107
+ ref={searchRef}
108
+ placeholder={messages.roadmap.searchPlaceholder}
109
+ onChangeText={(event) =>
110
+ setQuery(
111
+ (event as unknown as { nativeEvent: { text: string } }).nativeEvent
112
+ .text,
113
+ )
114
+ }
115
+ obscureBackground={false}
116
+ allowToolbarIntegration
117
+ hideNavigationBar={false}
118
+ textColor={theme.colors.text}
119
+ />
120
+ <Stack.Toolbar placement="bottom">
121
+ <Stack.Toolbar.SearchBarSlot />
122
+ </Stack.Toolbar>
63
123
  <RoadmapToolbar
64
124
  androidToolbarIcons={androidToolbarIcons}
65
125
  messages={messages}
66
126
  theme={theme}
67
127
  />
68
- <NativeRoadmapScreen {...props} />
128
+ <RoadmapScreenContent
129
+ pageSize={pageSize ?? hooks.pageSizes.roadmap}
130
+ entryPageSize={entryPageSize ?? hooks.pageSizes.entries}
131
+ query={query}
132
+ onQueryChange={setQuery}
133
+ showBoardHeader={false}
134
+ onEntryOpen={onEntryOpen}
135
+ onUnauthenticated={onUnauthenticated}
136
+ primaryColor={primaryColor}
137
+ primaryForeground={primaryForeground}
138
+ backgroundColor={backgroundColor}
139
+ surfaceColor={surfaceColor}
140
+ textColor={textColor}
141
+ mutedColor={mutedColor}
142
+ borderColor={borderColor}
143
+ dangerColor={dangerColor}
144
+ />
69
145
  </>
70
146
  );
71
147
  }
@@ -54,7 +54,6 @@ export function RoadmapStackLayout({
54
54
  };
55
55
  const defaults: NativeStackNavigationOptions = {
56
56
  headerShown: true,
57
- headerTransparent: true,
58
57
  headerShadowVisible: true,
59
58
  headerBackButtonDisplayMode: "minimal",
60
59
  headerTintColor: textColor ?? resolvedTheme.colors.text,
@@ -5,10 +5,12 @@ export * from "../../shared/metadata.js";
5
5
  export * from "../../shared/theme.js";
6
6
  export type * from "../../shared/types/index.js";
7
7
  export * from "../shared/ui/primitives.js";
8
+ export * from "../shared/ui/RoadmapBoard.js";
8
9
  export {
9
10
  RoadmapBoardContent,
10
11
  RoadmapItemContent,
11
12
  RoadmapProvider,
13
+ RoadmapScreenContent,
12
14
  } from "../shared/ui/RoadmapScreen.js";
13
15
  export * from "./metadata.js";
14
16
  export * from "./FeedbackScreen.js";
@@ -20,3 +20,8 @@ Containers such as `FeedbackEntry.Content`, `FeedbackForm.Root`, and `Comment.Ch
20
20
  and renders attached entries with the existing `EntryCard` and `EntryDetail`
21
21
  views. Pass `onUnauthenticated` to handle auth when an anonymous visitor votes
22
22
  or comments.
23
+
24
+ The exported `RoadmapBoard` is the reusable admin-style horizontal board
25
+ layout. Supply ordered stages, colors, and a `renderItem` callback to share the
26
+ same column scrolling behavior while customizing cards for an admin or public
27
+ surface.
@@ -5,6 +5,7 @@ export * from "../../shared/metadata.js";
5
5
  export * from "../../shared/theme.js";
6
6
  export type * from "../../shared/types/index.js";
7
7
  export * from "../shared/ui/primitives.js";
8
+ export * from "../shared/ui/RoadmapBoard.js";
8
9
  export * from "../shared/metadata.js";
9
10
  export * from "./FeedbackScreen.js";
10
11
  export * from "../shared/ui/RoadmapScreen.js";
@@ -10,6 +10,7 @@ export function EntryCard({
10
10
  entry,
11
11
  hooks,
12
12
  onOpen,
13
+ titleNumberOfLines,
13
14
  }: FeedbackScreenEntryCardProps) {
14
15
  const { isAuthenticated, onUnauthenticated } = useFeedbackBody();
15
16
  const setUpvote = hooks.useSetEntryUpvote();
@@ -46,7 +47,7 @@ export function EntryCard({
46
47
  <FeedbackEntry.Kind />
47
48
  <FeedbackEntry.Status />
48
49
  </View>
49
- <FeedbackEntry.Title />
50
+ <FeedbackEntry.Title numberOfLines={titleNumberOfLines} />
50
51
  <FeedbackEntry.Body numberOfLines={3} />
51
52
  <FeedbackEntry.CommentCount />
52
53
  </Pressable>
@@ -0,0 +1,225 @@
1
+ import type { RoadmapItem, RoadmapStatus } from "convex-feedback";
2
+ import type { ReactElement, ReactNode } from "react";
3
+ import {
4
+ ActivityIndicator,
5
+ FlatList,
6
+ Pressable,
7
+ ScrollView,
8
+ StyleSheet,
9
+ Text,
10
+ View,
11
+ type StyleProp,
12
+ type ViewProps,
13
+ type ViewStyle,
14
+ } from "react-native";
15
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
16
+
17
+ export interface RoadmapBoardStage {
18
+ value: RoadmapStatus;
19
+ label: string;
20
+ }
21
+
22
+ export interface RoadmapBoardColors {
23
+ background: string;
24
+ surface: string;
25
+ text: string;
26
+ muted: string;
27
+ border: string;
28
+ primary: string;
29
+ }
30
+
31
+ export interface RoadmapBoardItemRenderArgs {
32
+ item: RoadmapItem;
33
+ stage: RoadmapBoardStage;
34
+ stageIndex: number;
35
+ onOpen: () => void;
36
+ }
37
+
38
+ export interface RoadmapBoardProps {
39
+ /** Items are grouped into columns using their current status. */
40
+ items: readonly RoadmapItem[];
41
+ /** Ordered columns rendered from left to right. */
42
+ stages: readonly RoadmapBoardStage[];
43
+ /** Colors used by the shared board frame and its loading/empty states. */
44
+ colors: RoadmapBoardColors;
45
+ /** Shows the same full-board loading state as the admin roadmap. */
46
+ loading?: boolean;
47
+ /** Copy shown when a column does not contain any items. */
48
+ emptyLabel: string;
49
+ /** Opens a roadmap item from any rendered card. */
50
+ onItemOpen: (item: RoadmapItem) => void;
51
+ /** Supplies the card UI so admin and user-facing cards can differ. */
52
+ renderItem: (args: RoadmapBoardItemRenderArgs) => ReactElement | null;
53
+ style?: StyleProp<ViewStyle>;
54
+ contentContainerStyle?: StyleProp<ViewStyle>;
55
+ }
56
+
57
+ export interface RoadmapBoardCardProps extends Pick<
58
+ ViewProps,
59
+ "accessibilityLabel"
60
+ > {
61
+ colors: Pick<RoadmapBoardColors, "border" | "surface">;
62
+ onPress: () => void;
63
+ children: ReactNode;
64
+ style?: StyleProp<ViewStyle>;
65
+ }
66
+
67
+ /** Shared admin-style pressable card shell with configurable contents. */
68
+ export function RoadmapBoardCard({
69
+ colors,
70
+ onPress,
71
+ accessibilityLabel,
72
+ children,
73
+ style,
74
+ }: RoadmapBoardCardProps) {
75
+ return (
76
+ <Pressable
77
+ accessibilityRole="button"
78
+ accessibilityLabel={accessibilityLabel}
79
+ onPress={onPress}
80
+ style={({ pressed }) => [
81
+ styles.card,
82
+ {
83
+ borderColor: colors.border,
84
+ backgroundColor: colors.surface,
85
+ },
86
+ style,
87
+ pressed && styles.pressed,
88
+ ]}
89
+ >
90
+ {children}
91
+ </Pressable>
92
+ );
93
+ }
94
+
95
+ /**
96
+ * Shared stage-board layout used by the public roadmap and admin app.
97
+ *
98
+ * The outer list scrolls horizontally while each stage owns its vertical
99
+ * `FlatList`, matching the admin board's bounded nested-scroll structure.
100
+ */
101
+ export function RoadmapBoard({
102
+ items,
103
+ stages,
104
+ colors,
105
+ loading = false,
106
+ emptyLabel,
107
+ onItemOpen,
108
+ renderItem,
109
+ style,
110
+ contentContainerStyle,
111
+ }: RoadmapBoardProps) {
112
+ const insets = useSafeAreaInsets();
113
+
114
+ if (loading) {
115
+ return (
116
+ <ActivityIndicator
117
+ style={[styles.loader, { backgroundColor: colors.background }, style]}
118
+ color={colors.primary}
119
+ />
120
+ );
121
+ }
122
+
123
+ return (
124
+ <ScrollView
125
+ horizontal
126
+ style={[
127
+ styles.boardScroll,
128
+ { backgroundColor: colors.background },
129
+ style,
130
+ ]}
131
+ contentInsetAdjustmentBehavior="automatic"
132
+ showsHorizontalScrollIndicator={false}
133
+ contentContainerStyle={[
134
+ styles.board,
135
+ {
136
+ paddingLeft: 12 + insets.left,
137
+ paddingRight: 12 + insets.right,
138
+ paddingBottom: 24 + insets.bottom,
139
+ },
140
+ contentContainerStyle,
141
+ ]}
142
+ >
143
+ {stages.map((stage, stageIndex) => {
144
+ const stageItems = items
145
+ .filter((item) => item.status === stage.value)
146
+ .sort((a, b) => a.position - b.position);
147
+
148
+ return (
149
+ <View
150
+ key={stage.value}
151
+ style={[
152
+ styles.column,
153
+ {
154
+ borderColor: colors.border,
155
+ backgroundColor: colors.surface,
156
+ },
157
+ ]}
158
+ >
159
+ <View style={[styles.columnHeader, { borderColor: colors.border }]}>
160
+ <Text style={[styles.columnTitle, { color: colors.text }]}>
161
+ {stage.label}
162
+ </Text>
163
+ <Text style={[styles.count, { color: colors.muted }]}>
164
+ {stageItems.length}
165
+ </Text>
166
+ </View>
167
+ <FlatList
168
+ data={stageItems}
169
+ nestedScrollEnabled
170
+ contentInsetAdjustmentBehavior="never"
171
+ keyExtractor={(item) => item.id}
172
+ contentContainerStyle={[
173
+ styles.cards,
174
+ { paddingBottom: 16 + insets.bottom },
175
+ ]}
176
+ ListEmptyComponent={
177
+ <Text style={[styles.empty, { color: colors.muted }]}>
178
+ {emptyLabel}
179
+ </Text>
180
+ }
181
+ renderItem={({ item }) =>
182
+ renderItem({
183
+ item,
184
+ stage,
185
+ stageIndex,
186
+ onOpen: () => onItemOpen(item),
187
+ })
188
+ }
189
+ />
190
+ </View>
191
+ );
192
+ })}
193
+ </ScrollView>
194
+ );
195
+ }
196
+
197
+ const styles = StyleSheet.create({
198
+ boardScroll: { flex: 1 },
199
+ loader: { flex: 1 },
200
+ board: { gap: 12, paddingTop: 12 },
201
+ column: {
202
+ width: 310,
203
+ borderWidth: 1,
204
+ borderRadius: 16,
205
+ overflow: "hidden",
206
+ },
207
+ columnHeader: {
208
+ flexDirection: "row",
209
+ alignItems: "center",
210
+ justifyContent: "space-between",
211
+ borderBottomWidth: 1,
212
+ padding: 14,
213
+ },
214
+ columnTitle: { fontSize: 14, fontWeight: "700" },
215
+ count: { fontSize: 12 },
216
+ cards: { gap: 10, padding: 10 },
217
+ empty: { padding: 18, textAlign: "center" },
218
+ card: {
219
+ gap: 8,
220
+ borderWidth: 1,
221
+ borderRadius: 12,
222
+ padding: 13,
223
+ },
224
+ pressed: { opacity: 0.7 },
225
+ });