Skip to content

Commit 9e64cea

Browse files
committed
feat(expo): add attachments to the wizard's Details step
1 parent b2162d3 commit 9e64cea

2 files changed

Lines changed: 75 additions & 2 deletions

File tree

packages/expo/src/wizard/sheet.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import { useAnnotationShapes } from "../annotation/use-shapes"
1717
import { CloseIcon } from "../annotation/icons"
1818
import { PrimaryButton, SecondaryButton, StepIndicator } from "./controls"
1919
import { theme } from "./theme"
20+
import { pickFiles } from "../capture/file-picker"
21+
import { DEFAULT_ATTACHMENT_LIMITS, validateAttachments, type Attachment } from "@reprojs/sdk-utils"
2022

2123
export interface WizardArgs {
2224
initialTitle?: string
@@ -27,6 +29,7 @@ export interface WizardArgs {
2729
description: string
2830
annotatedUri: string | null
2931
rawUri: string | null
32+
attachments: Attachment[]
3033
}) => Promise<void>
3134
onClose: () => void
3235
}
@@ -48,6 +51,8 @@ export function WizardSheet({
4851
const [submitting, setSubmitting] = useState(false)
4952
const [error, setError] = useState<string | null>(null)
5053
const [annotateSize, setAnnotateSize] = useState({ w: 0, h: 0 })
54+
const [attachments, setAttachments] = useState<Attachment[]>([])
55+
const [attachmentErrors, setAttachmentErrors] = useState<string[]>([])
5156
const store = useRef(createAnnotationStore()).current
5257
const flattenRef = useRef<FlattenHandle | null>(null)
5358

@@ -58,6 +63,35 @@ export function WizardSheet({
5863
setStep("form")
5964
}, [screenshot])
6065

66+
async function handleAttachmentsAdd() {
67+
const picked = await pickFiles({ multiple: true })
68+
if (picked.length === 0) return
69+
// Convert picker output → File[] for validateAttachments. Each picked
70+
// Attachment already has size/mime/filename; we mirror them onto a stub
71+
// File so the validator's File contract type-checks. Override size since
72+
// the picker already told us the real value.
73+
const asFiles: File[] = picked.map((a) => {
74+
const f = new File([new Uint8Array(0)], a.filename, { type: a.mime })
75+
Object.defineProperty(f, "size", { value: a.size, configurable: true })
76+
return f
77+
})
78+
const result = validateAttachments(asFiles, attachments, DEFAULT_ATTACHMENT_LIMITS)
79+
// Reattach picker uri/blob to the validated Attachments (validateAttachments
80+
// wraps the File reference in `blob`, which on RN is a stub — replace with
81+
// the picker's previewUrl + blob).
82+
const accepted = result.accepted.map((a) => {
83+
const original = picked.find((p) => p.filename === a.filename)
84+
if (!original) return a
85+
return { ...a, blob: original.blob, previewUrl: original.previewUrl }
86+
})
87+
setAttachments((prev) => [...prev, ...accepted])
88+
setAttachmentErrors(result.rejected.map((r) => `${r.filename}: ${r.reason.replace("-", " ")}`))
89+
}
90+
91+
function handleAttachmentRemove(id: string) {
92+
setAttachments((prev) => prev.filter((a) => a.id !== id))
93+
}
94+
6195
async function handleSubmit() {
6296
setSubmitting(true)
6397
setError(null)
@@ -82,6 +116,7 @@ export function WizardSheet({
82116
description,
83117
annotatedUri: annotated,
84118
rawUri: screenshot?.uri ?? null,
119+
attachments,
85120
})
86121
} catch (e) {
87122
setError((e as Error).message)
@@ -122,8 +157,11 @@ export function WizardSheet({
122157
}
123158
lines.push({ label: "Console, network & breadcrumbs" })
124159
lines.push({ label: "Device & environment info" })
160+
if (attachments.length > 0) {
161+
lines.push({ label: "Additional attachments", hint: String(attachments.length) })
162+
}
125163
return lines
126-
}, [title, screenshot, shapes.length])
164+
}, [title, screenshot, shapes.length, attachments.length])
127165

128166
return (
129167
<Modal visible animationType="slide" onRequestClose={onClose} presentationStyle="pageSheet">
@@ -191,8 +229,12 @@ export function WizardSheet({
191229
<StepForm
192230
title={title}
193231
description={description}
232+
attachments={attachments}
233+
attachmentErrors={attachmentErrors}
194234
onTitleChange={setTitle}
195235
onDescriptionChange={setDescription}
236+
onAttachmentsAdd={handleAttachmentsAdd}
237+
onAttachmentRemove={handleAttachmentRemove}
196238
/>
197239
)}
198240
{step === "annotate" && (

packages/expo/src/wizard/step-form.tsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11
import React from "react"
22
import { ScrollView, TextInput, View } from "react-native"
33
import { FieldLabel, inputStyle } from "./controls"
4+
import { AttachmentList } from "./attachment-list"
45
import { theme } from "./theme"
6+
import {
7+
DEFAULT_ATTACHMENT_LIMITS,
8+
type Attachment,
9+
type AttachmentLimits,
10+
} from "@reprojs/sdk-utils"
511

612
interface Props {
713
title: string
814
description: string
15+
attachments: Attachment[]
16+
attachmentErrors: string[]
17+
limits?: AttachmentLimits
918
onTitleChange: (v: string) => void
1019
onDescriptionChange: (v: string) => void
20+
onAttachmentsAdd: () => void
21+
onAttachmentRemove: (id: string) => void
1122
}
1223

13-
export function StepForm({ title, description, onTitleChange, onDescriptionChange }: Props) {
24+
export function StepForm({
25+
title,
26+
description,
27+
attachments,
28+
attachmentErrors,
29+
limits = DEFAULT_ATTACHMENT_LIMITS,
30+
onTitleChange,
31+
onDescriptionChange,
32+
onAttachmentsAdd,
33+
onAttachmentRemove,
34+
}: Props) {
1435
return (
1536
<ScrollView
1637
style={{ flex: 1 }}
@@ -42,6 +63,16 @@ export function StepForm({ title, description, onTitleChange, onDescriptionChang
4263
style={[inputStyle, { minHeight: 140, textAlignVertical: "top" }]}
4364
/>
4465
</View>
66+
<View style={{ gap: 8 }}>
67+
<FieldLabel label="Attachments" optional />
68+
<AttachmentList
69+
attachments={attachments}
70+
limits={limits}
71+
errors={attachmentErrors}
72+
onAdd={onAttachmentsAdd}
73+
onRemove={onAttachmentRemove}
74+
/>
75+
</View>
4576
</ScrollView>
4677
)
4778
}

0 commit comments

Comments
 (0)