@upstep/react-native 0.5.1 → 0.6.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.
- package/package.json +2 -5
- package/src/FeedbackContext.tsx +9 -8
- package/src/FeedbackSheet.tsx +151 -137
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upstep/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Upstep feedback SDK for React Native",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -15,11 +15,8 @@
|
|
|
15
15
|
"@upstep/types": "0.5.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@gorhom/bottom-sheet": ">=4",
|
|
19
18
|
"react": ">=18",
|
|
20
|
-
"react-native": ">=0.73"
|
|
21
|
-
"react-native-gesture-handler": ">=2",
|
|
22
|
-
"react-native-reanimated": ">=3"
|
|
19
|
+
"react-native": ">=0.73"
|
|
23
20
|
},
|
|
24
21
|
"devDependencies": {
|
|
25
22
|
"@types/react": "^19.0.7",
|
package/src/FeedbackContext.tsx
CHANGED
|
@@ -4,7 +4,6 @@ import React, {
|
|
|
4
4
|
useEffect,
|
|
5
5
|
useState,
|
|
6
6
|
useCallback,
|
|
7
|
-
useRef,
|
|
8
7
|
type ReactNode,
|
|
9
8
|
} from "react";
|
|
10
9
|
import type {
|
|
@@ -15,7 +14,6 @@ import type {
|
|
|
15
14
|
VoteValue,
|
|
16
15
|
} from "@upstep/types";
|
|
17
16
|
import { UpstepApiClient } from "./api";
|
|
18
|
-
import type BottomSheet from "@gorhom/bottom-sheet";
|
|
19
17
|
|
|
20
18
|
const DEFAULT_ACCENT = "#D97757";
|
|
21
19
|
|
|
@@ -27,11 +25,10 @@ interface UpstepContextValue {
|
|
|
27
25
|
submit: (title: string, content: string, type?: FeedbackType) => Promise<void>;
|
|
28
26
|
vote: (feedbackId: string, value: VoteValue) => Promise<void>;
|
|
29
27
|
getItem: (feedbackId: string) => Promise<FeedbackWithComments>;
|
|
30
|
-
|
|
28
|
+
isOpen: boolean;
|
|
31
29
|
openSheet: () => void;
|
|
32
|
-
|
|
30
|
+
closeSheet: () => void;
|
|
33
31
|
identify: (userId: string | undefined) => void;
|
|
34
|
-
sheetRef: React.RefObject<BottomSheet | null>;
|
|
35
32
|
accentColor: string;
|
|
36
33
|
theme: "light" | "dark" | "auto";
|
|
37
34
|
}
|
|
@@ -45,7 +42,7 @@ export function FeedbackProvider({
|
|
|
45
42
|
const [client] = useState(() => new UpstepApiClient(config));
|
|
46
43
|
const [feedItems, setFeedItems] = useState<Feedback[]>([]);
|
|
47
44
|
const [feedLoading, setFeedLoading] = useState(true);
|
|
48
|
-
const
|
|
45
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
49
46
|
|
|
50
47
|
useEffect(() => {
|
|
51
48
|
client.setUserId(config.userId);
|
|
@@ -87,7 +84,11 @@ export function FeedbackProvider({
|
|
|
87
84
|
);
|
|
88
85
|
|
|
89
86
|
const openSheet = useCallback(() => {
|
|
90
|
-
|
|
87
|
+
setIsOpen(true);
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
const closeSheet = useCallback(() => {
|
|
91
|
+
setIsOpen(false);
|
|
91
92
|
}, []);
|
|
92
93
|
|
|
93
94
|
const identify = useCallback(
|
|
@@ -99,7 +100,7 @@ export function FeedbackProvider({
|
|
|
99
100
|
<UpstepContext.Provider
|
|
100
101
|
value={{
|
|
101
102
|
client, feedItems, feedLoading, loadFeed, submit, vote, getItem,
|
|
102
|
-
openSheet,
|
|
103
|
+
isOpen, openSheet, closeSheet, identify,
|
|
103
104
|
accentColor: config.accentColor ?? DEFAULT_ACCENT,
|
|
104
105
|
theme: config.theme ?? "auto",
|
|
105
106
|
}}
|
package/src/FeedbackSheet.tsx
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import React, { useState,
|
|
1
|
+
import React, { useState, useCallback, useEffect } from "react";
|
|
2
2
|
import {
|
|
3
|
+
Modal,
|
|
3
4
|
View,
|
|
4
5
|
Text,
|
|
5
6
|
TextInput,
|
|
6
7
|
TouchableOpacity,
|
|
8
|
+
Pressable,
|
|
9
|
+
ScrollView,
|
|
10
|
+
KeyboardAvoidingView,
|
|
7
11
|
StyleSheet,
|
|
8
12
|
ActivityIndicator,
|
|
9
13
|
useColorScheme,
|
|
10
14
|
Platform,
|
|
15
|
+
SafeAreaView,
|
|
11
16
|
} from "react-native";
|
|
12
|
-
import BottomSheet, {
|
|
13
|
-
BottomSheetView,
|
|
14
|
-
BottomSheetScrollView,
|
|
15
|
-
BottomSheetBackdrop,
|
|
16
|
-
} from "@gorhom/bottom-sheet";
|
|
17
|
-
import type { BottomSheetBackdropProps } from "@gorhom/bottom-sheet";
|
|
18
17
|
import type { Comment, Feedback, FeedbackType, FeedbackWithComments } from "@upstep/types";
|
|
19
18
|
import { useUpstep } from "./FeedbackContext";
|
|
20
19
|
|
|
@@ -58,116 +57,119 @@ type Nav =
|
|
|
58
57
|
| { screen: "create" };
|
|
59
58
|
|
|
60
59
|
// ─── FeedbackSheet ────────────────────────────────────────────────────────────
|
|
61
|
-
//
|
|
62
|
-
// IMPORTANT: BottomSheet requires stable direct children — never conditionally
|
|
63
|
-
// render <BottomSheetView> or <BottomSheetScrollView> as they set up gesture
|
|
64
|
-
// handlers on mount. We keep ONE of each always mounted; only their contents
|
|
65
|
-
// change based on `nav`. This also fixes TouchableOpacity not responding.
|
|
66
60
|
|
|
67
61
|
export function FeedbackSheet() {
|
|
68
|
-
const {
|
|
62
|
+
const { isOpen, closeSheet, accentColor, theme, loadFeed } = useUpstep();
|
|
69
63
|
const scheme = useColorScheme();
|
|
70
64
|
const isDark = theme === "dark" || (theme === "auto" && scheme === "dark");
|
|
71
65
|
const p = isDark ? DARK : LIGHT;
|
|
72
66
|
|
|
73
67
|
const [nav, setNav] = useState<Nav>({ screen: "list" });
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
),
|
|
86
|
-
[]
|
|
87
|
-
);
|
|
68
|
+
|
|
69
|
+
// Refresh list every time the modal opens
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (isOpen) loadFeed();
|
|
72
|
+
}, [isOpen, loadFeed]);
|
|
73
|
+
|
|
74
|
+
function handleClose() {
|
|
75
|
+
closeSheet();
|
|
76
|
+
// Reset to list when closed so next open starts fresh
|
|
77
|
+
setTimeout(() => setNav({ screen: "list" }), 300);
|
|
78
|
+
}
|
|
88
79
|
|
|
89
80
|
return (
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
backgroundStyle={{ backgroundColor: p.bg, borderRadius: 24 }}
|
|
97
|
-
handleIndicatorStyle={{ backgroundColor: p.handle, width: 40, height: 4 }}
|
|
98
|
-
onChange={(index) => { if (index >= 0) loadFeed(); }}
|
|
99
|
-
onClose={() => setNav({ screen: "list" })}
|
|
81
|
+
<Modal
|
|
82
|
+
visible={isOpen}
|
|
83
|
+
animationType="slide"
|
|
84
|
+
transparent
|
|
85
|
+
statusBarTranslucent
|
|
86
|
+
onRequestClose={handleClose}
|
|
100
87
|
>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
88
|
+
<KeyboardAvoidingView
|
|
89
|
+
style={styles.overlay}
|
|
90
|
+
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
91
|
+
>
|
|
92
|
+
{/* Tap backdrop to dismiss */}
|
|
93
|
+
<Pressable style={styles.backdrop} onPress={handleClose} />
|
|
94
|
+
|
|
95
|
+
{/* Sheet */}
|
|
96
|
+
<View style={[styles.sheet, { backgroundColor: p.bg }]}>
|
|
97
|
+
{/* Drag handle */}
|
|
98
|
+
<View style={styles.handleWrap}>
|
|
99
|
+
<View style={[styles.handle, { backgroundColor: p.handle }]} />
|
|
100
|
+
</View>
|
|
101
|
+
|
|
102
|
+
{/* Fixed header */}
|
|
103
|
+
<View style={[styles.header, { borderBottomColor: p.border }]}>
|
|
104
|
+
{nav.screen === "list" ? (
|
|
105
|
+
<>
|
|
106
|
+
<Text style={[styles.headerTitle, { color: p.text }]}>Feedback</Text>
|
|
107
|
+
<TouchableOpacity
|
|
108
|
+
onPress={() => setNav({ screen: "create" })}
|
|
109
|
+
activeOpacity={0.8}
|
|
110
|
+
style={[styles.newBtn, { backgroundColor: accentColor }]}
|
|
111
|
+
>
|
|
112
|
+
<Text style={styles.newBtnText}>+ New</Text>
|
|
113
|
+
</TouchableOpacity>
|
|
114
|
+
</>
|
|
115
|
+
) : nav.screen === "create" ? (
|
|
116
|
+
<>
|
|
117
|
+
<TouchableOpacity
|
|
118
|
+
onPress={() => setNav({ screen: "list" })}
|
|
119
|
+
hitSlop={{ top: 10, bottom: 10, left: 10, right: 16 }}
|
|
120
|
+
>
|
|
121
|
+
<Text style={[styles.navBtn, { color: accentColor }]}>Cancel</Text>
|
|
122
|
+
</TouchableOpacity>
|
|
123
|
+
<Text style={[styles.headerTitle, { color: p.text }]}>New feedback</Text>
|
|
124
|
+
<View style={styles.navBtnSpacer} />
|
|
125
|
+
</>
|
|
126
|
+
) : (
|
|
122
127
|
<TouchableOpacity
|
|
123
128
|
onPress={() => setNav({ screen: "list" })}
|
|
124
|
-
hitSlop={{ top:
|
|
129
|
+
hitSlop={{ top: 10, bottom: 10, left: 10, right: 16 }}
|
|
125
130
|
>
|
|
126
|
-
<Text style={[styles.navBtn, { color: accentColor }]}
|
|
131
|
+
<Text style={[styles.navBtn, { color: accentColor }]}>← Back</Text>
|
|
127
132
|
</TouchableOpacity>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
)}
|
|
134
|
+
</View>
|
|
135
|
+
|
|
136
|
+
{/* Scrollable body */}
|
|
137
|
+
<ScrollView
|
|
138
|
+
style={styles.scroll}
|
|
139
|
+
contentContainerStyle={styles.scrollContent}
|
|
140
|
+
keyboardShouldPersistTaps="handled"
|
|
141
|
+
showsVerticalScrollIndicator={false}
|
|
142
|
+
>
|
|
143
|
+
{nav.screen === "list" && (
|
|
144
|
+
<FeedListBody
|
|
145
|
+
p={p}
|
|
146
|
+
accent={accentColor}
|
|
147
|
+
onSelect={(id) => setNav({ screen: "detail", feedbackId: id })}
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
150
|
+
{nav.screen === "detail" && "feedbackId" in nav && (
|
|
151
|
+
<FeedDetailBody
|
|
152
|
+
p={p}
|
|
153
|
+
accent={accentColor}
|
|
154
|
+
feedbackId={(nav as { screen: "detail"; feedbackId: string }).feedbackId}
|
|
155
|
+
/>
|
|
156
|
+
)}
|
|
157
|
+
{nav.screen === "create" && (
|
|
158
|
+
<FeedCreateBody
|
|
159
|
+
p={p}
|
|
160
|
+
accent={accentColor}
|
|
161
|
+
onDone={() => {
|
|
162
|
+
setNav({ screen: "list" });
|
|
163
|
+
}}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
166
|
+
</ScrollView>
|
|
167
|
+
|
|
168
|
+
{/* Safe area spacer */}
|
|
169
|
+
<SafeAreaView />
|
|
139
170
|
</View>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<BottomSheetScrollView
|
|
143
|
-
style={styles.scrollView}
|
|
144
|
-
contentContainerStyle={styles.scrollBody}
|
|
145
|
-
keyboardShouldPersistTaps="handled"
|
|
146
|
-
>
|
|
147
|
-
{nav.screen === "list" && (
|
|
148
|
-
<FeedListBody
|
|
149
|
-
p={p}
|
|
150
|
-
accent={accentColor}
|
|
151
|
-
onSelect={(id) => setNav({ screen: "detail", feedbackId: id })}
|
|
152
|
-
/>
|
|
153
|
-
)}
|
|
154
|
-
{nav.screen === "detail" && "feedbackId" in nav && (
|
|
155
|
-
<FeedDetailBody
|
|
156
|
-
p={p}
|
|
157
|
-
accent={accentColor}
|
|
158
|
-
feedbackId={(nav as { screen: "detail"; feedbackId: string }).feedbackId}
|
|
159
|
-
/>
|
|
160
|
-
)}
|
|
161
|
-
{nav.screen === "create" && (
|
|
162
|
-
<FeedCreateBody
|
|
163
|
-
p={p}
|
|
164
|
-
accent={accentColor}
|
|
165
|
-
onDone={() => setNav({ screen: "list" })}
|
|
166
|
-
/>
|
|
167
|
-
)}
|
|
168
|
-
</BottomSheetScrollView>
|
|
169
|
-
</BottomSheetView>
|
|
170
|
-
</BottomSheet>
|
|
171
|
+
</KeyboardAvoidingView>
|
|
172
|
+
</Modal>
|
|
171
173
|
);
|
|
172
174
|
}
|
|
173
175
|
|
|
@@ -176,8 +178,7 @@ export function FeedbackSheet() {
|
|
|
176
178
|
function FeedListBody({
|
|
177
179
|
p, accent, onSelect,
|
|
178
180
|
}: {
|
|
179
|
-
p: Palette; accent: string;
|
|
180
|
-
onSelect: (id: string) => void;
|
|
181
|
+
p: Palette; accent: string; onSelect: (id: string) => void;
|
|
181
182
|
}) {
|
|
182
183
|
const { feedItems, feedLoading, vote } = useUpstep();
|
|
183
184
|
|
|
@@ -200,7 +201,7 @@ function FeedListBody({
|
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
return (
|
|
203
|
-
|
|
204
|
+
<>
|
|
204
205
|
{feedItems.map((f) => (
|
|
205
206
|
<FeedCard
|
|
206
207
|
key={f.id}
|
|
@@ -211,7 +212,7 @@ function FeedListBody({
|
|
|
211
212
|
onVote={() => vote(f.id, "UP")}
|
|
212
213
|
/>
|
|
213
214
|
))}
|
|
214
|
-
|
|
215
|
+
</>
|
|
215
216
|
);
|
|
216
217
|
}
|
|
217
218
|
|
|
@@ -219,8 +220,7 @@ function FeedCard({
|
|
|
219
220
|
item, p, accent, onPress, onVote,
|
|
220
221
|
}: {
|
|
221
222
|
item: Feedback; p: Palette; accent: string;
|
|
222
|
-
onPress: () => void;
|
|
223
|
-
onVote: () => void;
|
|
223
|
+
onPress: () => void; onVote: () => void;
|
|
224
224
|
}) {
|
|
225
225
|
const voted = item.userVote === "UP";
|
|
226
226
|
const displayTitle = item.title ?? item.content;
|
|
@@ -259,9 +259,7 @@ function FeedCard({
|
|
|
259
259
|
<View style={styles.cardMeta}>
|
|
260
260
|
<TypeBadge type={item.type} p={p} />
|
|
261
261
|
<StatusDot status={item.status} />
|
|
262
|
-
<Text style={[styles.cardDate, { color: p.textFaint }]}>
|
|
263
|
-
{fmtDate(item.createdAt)}
|
|
264
|
-
</Text>
|
|
262
|
+
<Text style={[styles.cardDate, { color: p.textFaint }]}>{fmtDate(item.createdAt)}</Text>
|
|
265
263
|
</View>
|
|
266
264
|
</View>
|
|
267
265
|
|
|
@@ -350,9 +348,7 @@ function FeedDetailBody({
|
|
|
350
348
|
|
|
351
349
|
{item.comments.length > 0 && (
|
|
352
350
|
<View style={[styles.commentsSection, { borderTopColor: p.border }]}>
|
|
353
|
-
<Text style={[styles.commentsLabel, { color: p.textFaint }]}>
|
|
354
|
-
DEVELOPER RESPONSE
|
|
355
|
-
</Text>
|
|
351
|
+
<Text style={[styles.commentsLabel, { color: p.textFaint }]}>DEVELOPER RESPONSE</Text>
|
|
356
352
|
{item.comments.map((c) => (
|
|
357
353
|
<CommentBubble key={c.id} comment={c} p={p} accent={accent} />
|
|
358
354
|
))}
|
|
@@ -371,9 +367,7 @@ function CommentBubble({ comment, p, accent }: { comment: Comment; p: Palette; a
|
|
|
371
367
|
{comment.authorName ?? "Team"}
|
|
372
368
|
</Text>
|
|
373
369
|
</View>
|
|
374
|
-
<Text style={[styles.commentDate, { color: p.textFaint }]}>
|
|
375
|
-
{fmtDate(comment.createdAt)}
|
|
376
|
-
</Text>
|
|
370
|
+
<Text style={[styles.commentDate, { color: p.textFaint }]}>{fmtDate(comment.createdAt)}</Text>
|
|
377
371
|
</View>
|
|
378
372
|
<Text style={[styles.commentContent, { color: p.textSoft }]}>{comment.content}</Text>
|
|
379
373
|
</View>
|
|
@@ -409,7 +403,7 @@ function FeedCreateBody({
|
|
|
409
403
|
try {
|
|
410
404
|
await submit(title.trim(), description.trim() || title.trim(), type);
|
|
411
405
|
setSuccess(true);
|
|
412
|
-
setTimeout(onDone,
|
|
406
|
+
setTimeout(onDone, 1200);
|
|
413
407
|
} catch {
|
|
414
408
|
setError("Something went wrong. Please try again.");
|
|
415
409
|
} finally {
|
|
@@ -419,7 +413,7 @@ function FeedCreateBody({
|
|
|
419
413
|
|
|
420
414
|
if (success) {
|
|
421
415
|
return (
|
|
422
|
-
<View style={[styles.centered, { paddingTop:
|
|
416
|
+
<View style={[styles.centered, { paddingTop: 40 }]}>
|
|
423
417
|
<Text style={[styles.successTitle, { color: p.text }]}>Submitted</Text>
|
|
424
418
|
<Text style={[styles.successSub, { color: p.textFaint }]}>
|
|
425
419
|
Thanks — we received your feedback.
|
|
@@ -519,11 +513,8 @@ function TypeBadge({ type, p }: { type: FeedbackType; p: Palette }) {
|
|
|
519
513
|
}
|
|
520
514
|
|
|
521
515
|
const STATUS_COLOR: Record<string, string> = {
|
|
522
|
-
OPEN: "#b45309",
|
|
523
|
-
|
|
524
|
-
DONE: "#15803d",
|
|
525
|
-
PENDING: "#ea580c",
|
|
526
|
-
CLOSED: "#6b7280",
|
|
516
|
+
OPEN: "#b45309", IN_PROGRESS: "#1d4ed8", DONE: "#15803d",
|
|
517
|
+
PENDING: "#ea580c", CLOSED: "#6b7280",
|
|
527
518
|
};
|
|
528
519
|
|
|
529
520
|
function StatusDot({ status }: { status: string }) {
|
|
@@ -534,9 +525,7 @@ function StatusBadge({ status, p }: { status: string; p: Palette }) {
|
|
|
534
525
|
return (
|
|
535
526
|
<View style={[styles.statusBadge, { backgroundColor: p.bgSoft, borderColor: p.border }]}>
|
|
536
527
|
<StatusDot status={status} />
|
|
537
|
-
<Text style={[styles.statusBadgeText, { color: p.textSoft }]}>
|
|
538
|
-
{status.replace("_", " ")}
|
|
539
|
-
</Text>
|
|
528
|
+
<Text style={[styles.statusBadgeText, { color: p.textSoft }]}>{status.replace("_", " ")}</Text>
|
|
540
529
|
</View>
|
|
541
530
|
);
|
|
542
531
|
}
|
|
@@ -548,9 +537,35 @@ function fmtDate(iso: string) {
|
|
|
548
537
|
// ─── Styles ───────────────────────────────────────────────────────────────────
|
|
549
538
|
|
|
550
539
|
const styles = StyleSheet.create({
|
|
551
|
-
//
|
|
552
|
-
|
|
540
|
+
// Modal overlay
|
|
541
|
+
overlay: {
|
|
542
|
+
flex: 1,
|
|
543
|
+
justifyContent: "flex-end",
|
|
544
|
+
},
|
|
545
|
+
backdrop: {
|
|
546
|
+
...StyleSheet.absoluteFill,
|
|
547
|
+
backgroundColor: "rgba(0,0,0,0.45)",
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
// Sheet container
|
|
551
|
+
sheet: {
|
|
552
|
+
maxHeight: "92%",
|
|
553
|
+
borderTopLeftRadius: 24,
|
|
554
|
+
borderTopRightRadius: 24,
|
|
555
|
+
overflow: "hidden",
|
|
556
|
+
},
|
|
557
|
+
handleWrap: {
|
|
558
|
+
alignItems: "center",
|
|
559
|
+
paddingTop: 10,
|
|
560
|
+
paddingBottom: 4,
|
|
561
|
+
},
|
|
562
|
+
handle: {
|
|
563
|
+
width: 40,
|
|
564
|
+
height: 4,
|
|
565
|
+
borderRadius: 2,
|
|
566
|
+
},
|
|
553
567
|
|
|
568
|
+
// Header
|
|
554
569
|
header: {
|
|
555
570
|
flexDirection: "row",
|
|
556
571
|
alignItems: "center",
|
|
@@ -561,15 +576,15 @@ const styles = StyleSheet.create({
|
|
|
561
576
|
},
|
|
562
577
|
headerTitle: { fontSize: 16, fontWeight: "700", letterSpacing: -0.3 },
|
|
563
578
|
navBtn: { fontSize: 14, fontWeight: "600" },
|
|
564
|
-
|
|
579
|
+
navBtnSpacer: { width: 60 },
|
|
565
580
|
newBtn: { borderRadius: 20, paddingHorizontal: 14, paddingVertical: 7 },
|
|
566
581
|
newBtnText: { color: "#fff", fontSize: 13, fontWeight: "700" },
|
|
567
582
|
|
|
568
|
-
|
|
569
|
-
|
|
583
|
+
// Scroll
|
|
584
|
+
scroll: { flexGrow: 0 },
|
|
585
|
+
scrollContent: { flexGrow: 1, padding: 16, paddingBottom: 32 },
|
|
570
586
|
|
|
571
587
|
// List
|
|
572
|
-
listPad: { padding: 16, paddingBottom: 40 },
|
|
573
588
|
centered: { alignItems: "center", justifyContent: "center", paddingVertical: 60 },
|
|
574
589
|
emptyText: { fontSize: 14 },
|
|
575
590
|
card: {
|
|
@@ -599,7 +614,7 @@ const styles = StyleSheet.create({
|
|
|
599
614
|
chevron: { fontSize: 22, fontWeight: "300", marginLeft: 4 },
|
|
600
615
|
|
|
601
616
|
// Detail
|
|
602
|
-
detailPad: {
|
|
617
|
+
detailPad: { paddingTop: 4 },
|
|
603
618
|
detailTitle: { fontSize: 22, fontWeight: "700", letterSpacing: -0.4, marginBottom: 10, lineHeight: 28 },
|
|
604
619
|
badgeRow: { flexDirection: "row", gap: 8, marginBottom: 16 },
|
|
605
620
|
detailContent: { fontSize: 15, lineHeight: 23, marginBottom: 24 },
|
|
@@ -615,8 +630,7 @@ const styles = StyleSheet.create({
|
|
|
615
630
|
commentsSection: { marginTop: 28, borderTopWidth: StyleSheet.hairlineWidth, paddingTop: 20 },
|
|
616
631
|
commentsLabel: { fontSize: 10, fontWeight: "700", letterSpacing: 0.8, marginBottom: 12 },
|
|
617
632
|
commentBubble: {
|
|
618
|
-
borderRadius: 12, borderWidth: StyleSheet.hairlineWidth,
|
|
619
|
-
padding: 14, marginBottom: 10,
|
|
633
|
+
borderRadius: 12, borderWidth: StyleSheet.hairlineWidth, padding: 14, marginBottom: 10,
|
|
620
634
|
},
|
|
621
635
|
commentMeta: { flexDirection: "row", alignItems: "center", gap: 8, marginBottom: 8 },
|
|
622
636
|
ownerBadge: { borderRadius: 20, paddingHorizontal: 8, paddingVertical: 3 },
|
|
@@ -625,7 +639,7 @@ const styles = StyleSheet.create({
|
|
|
625
639
|
commentContent: { fontSize: 14, lineHeight: 21 },
|
|
626
640
|
|
|
627
641
|
// Create
|
|
628
|
-
createPad: {
|
|
642
|
+
createPad: { paddingTop: 4 },
|
|
629
643
|
fieldLabel: { fontSize: 10, fontWeight: "700", letterSpacing: 0.8, marginBottom: 8 },
|
|
630
644
|
typeRow: { flexDirection: "row", gap: 8, marginBottom: 20, flexWrap: "wrap" },
|
|
631
645
|
typeBtn: { paddingVertical: 9, paddingHorizontal: 14, borderRadius: 10, borderWidth: 1 },
|