@innspel/react-native
v0.11.1
Published
Innsendingswidgeten for React Native. Dialog med fokusfelle, kjente feil før feltet, offline-kø gjort synlig.
Maintainers
Readme
@innspel/react-native
Innsendingswidgeten for React Native. Et ark over vertsappen din — aldri en flytende knapp over innholdet.
npm install @innspel/react-native @innspel/core react-native-safe-area-context react-native-svg
cd ios && pod install # bare i en bar React Native-appI Expo: npx expo install react-native-safe-area-context react-native-svg, så
versjonene passer SDK-et.
react, react-native, react-native-safe-area-context (4.0 eller nyere) og
react-native-svg (15.2 eller nyere) er peer-avhengigheter. Widgeten krever ikke
Expo, men virker i Expo.
Hvorfor react-native-svg: ikonene er Ionicons, inlinet som SVG i pakken — ikke emoji. En emoji tegnes av operativsystemet i sine egne faste farger, den ser ulik ut på iOS og Android, og en skjermleser leser den opp som navnet sitt («marihøne» for 🐞). Pakken installerer ingen ikonfont og laster ingenting over nett. Mangler avhengigheten, feiler byggetrinnet med navnet på den — widgeten blir aldri stående med usynlige ikoner i drift.
Hvorfor safe-area-context: på edge-to-edge (Android 15+, Expo SDK 53+) tegner
React Natives Modal under status- og navigasjonslinja uansett props, og React
Native har ingen egen måte å lese de linjenes høyde på Android. Uten insets havner
Send-knappen under navigasjonslinja. Har appen en SafeAreaProvider (expo-router har
det), leses insetsene derfra; ellers brukes vinduets insets fra oppstart. Du trenger
ikke legge til en provider for widgetens skyld. Arket løftes også over tastaturet,
siden vinduet ikke lenger krymper for det på edge-to-edge.
Quickstart
import AsyncStorage from '@react-native-async-storage/async-storage';
import { init } from '@innspel/core';
import { createStorage, InnspelProvider, useInnspel } from '@innspel/react-native';
const innspel = init({
baseUrl: 'https://api.innspel.example',
tenantKey: 'ik_pub_xxxxxxxx',
identityProvider: hentFeedbackToken, // eller null = anonym modus
storage: createStorage(AsyncStorage),
context: { platform: 'ios', appVersion: '1.4.2' },
});
export default () => (
<InnspelProvider client={innspel}>
<MinApp />
</InnspelProvider>
);Vil du at plattformen skal telle tragten (åpnet → spor valgt → sendt), gi
init() mottaket fra understien — anonymt, aldri tekst:
import { createIngestTelemetry } from '@innspel/core/telemetri';
// … telemetry: createIngestTelemetry({ baseUrl, tenantKey, platform: 'ios', appVersion: '1.4.2' }),Åpne widgeten hvor som helst under provideren:
const { open } = useInnspel();
<Button title="Send innspill" onPress={() => open({ screen: 'Dørlista' })} />;screen er rutens NAVN, aldri data i den. Det styrer hvilke kjente feil
innsenderen får se før hun skriver.
Utenfor React — en navigasjonshandler, en krasjlytter — bruker du klienten
direkte: innspel.openFeedback({ screen: 'Dørlista' }).
Lukking — X, eller Android-tilbake på første steg — beholder det innsenderen har
skrevet til widgeten åpnes igjen; bare en fullført innsending nullstiller. Åpner du med
track mens det står et utkast, står hun der hun var. view: 'mine' legges over
utkastet, og «Tilbake» i lista lukker.
Legg provideren rundt appen, ikke inni en ScrollView. Arket er en Modal, men
React Natives trykkhåndtering går gjennom React-treet: en ScrollView over provideren
med keyboardShouldPersistTaps på standardverdien tar første trykk mens tastaturet er
oppe, og bruker det til å lukke tastaturet. Send teller det trykket likevel; de andre
knappene i arket gjør ikke. Må provideren stå inni en scrollflate, gi den
keyboardShouldPersistTaps="handled".
Token-endepunktet
Det eneste du må bygge selv. Se
integrasjonsguiden, som viser det med kjørbar
ES256-kode. identityProvider: null er anonym modus — et eksplisitt valg, ikke
noe du havner i ved å glemme opsjonen.
Tema
Widgeten arver vertsappens flate. Send det du vil overstyre; resten faller tilbake på Innspels eget tema, som er målt mot WCAG AA i begge paletter.
<InnspelProvider
client={innspel}
colorScheme="light"
theme={{ color: { brand: '#E5007D', actionPrimary: '#E5007D' } }}
tenantName="Følget"
>tenantName er navnet som står på svaret i «Mine innspill» — ditt, ikke
Innspels. Innsenderen skal se hvem som svarte.
fontFamily finnes ikke som token: systemfonten er standard i React Native, og
å sette den ville brutt med appen rundt.
Når et innspill er sendt
onSubmitted kalles etter en innsending — også når nettet var borte og innspillet
ligger i køen, for innsenderen trykket Send. Du får type, queued, og id når
saken finnes hos tjeneren. Ingen identitet: saksnummeret er sakens, og det
innsenderen alt ser i kvitteringen. «Ja, jeg også» på en kjent feil gir ikke kallet.
<InnspelProvider
client={innspel}
onSubmitted={({ queued }) => {
hentTellingPaaNytt(); // «Mine innspill · N» i din egen meny
if (!queued) spørOmVarslerHvisFørste(); // riktig øyeblikk — ikke ved oppstart
}}
>Hva innsenderen møter
| Skjerm | Hva | |---|---| | Første valg | «Noe virker ikke» eller «Jeg ønsker meg noe». Aldri et tredje, aldri spørsmål om kategori eller alvorlighet. | | Kjente feil | I feilsporet før feltet, i ønskesporet etter. Tre svar per kort: «Ja, jeg også» (teller deg uten ny sak), «Ligner, men ikke helt» (ditt eget innspill med lenke), «Nei, det er noe annet». Tom liste hopper skjermen over — treffene er et tilbud, aldri en sperre. | | Feltet | Ett tekstfelt. Send er aktiv fra N tegn, og ingenting annet er obligatorisk. Konteksten som legges ved er synlig og kan fjernes linje for linje. | | Mønstervarsel | Ser teksten ut til å inneholde e-post, telefonnummer, kortnummer, IP eller en tilgangsnøkkel, spør widgeten om det skal fjernes. Den fjerner aldri noe selv. | | Skjermbilde | Valgfritt, én eller to veier: «Legg ved skjermbilde» (skjermen bak arket, tatt ved trykk) og «Velg bilde» (fra galleriet, med din velger). Forhåndsvises og maskeres på enheten — private views automatisk på en fangst, resten med felt, pensel eller «Masker all tekst». Se under. | | Kvittering | Lover noe konkret, per spor, med saksnummer. Ber aldri om en vurdering av appen. Feilet bildet, sier den det — saken er sendt uansett. | | Mine innspill | Status, løftet som hører til, og svaret fra deg under saken. Det som venter på nett står øverst. «Legg til mer» på en åpen sak: ett felt i kortet, maks tre tillegg, aldri et svar på svaret — tilleggene står under innsenderens egen tekst, og i panelet på saken. |
Offline
Faller nettet, køes innspillet lokalt — kun teksten. Køen tømmes når widgeten åpnes eller nettet kommer tilbake, aldri ved oppstart, og innsenderen ser hele tiden at det ligger noe og venter. Taket er 30.
Vil dere at widgeten skal si «Ingen nett» før innsenderen trykker, send inn
nettstatus i init({ connectivity }) — for eksempel fra
@react-native-community/netinfo. Pakken drar ikke inn den selv. Se
docs/sdk/README.md, «Nettstatus».
Tilgjengelighet
Widgeten er en dialog og oppfører seg som en: modal for skjermlesere, lukkes med Android-tilbake og ESC, kvitteringen annonseres, og hver trykkflate er minst 48 punkter. Ikoner er skjult for skjermlesere — etiketten bærer meningen, aldri fargen alene.
WCAG 2.1 AA er grønt-kriterium, ikke en ambisjon. Kontrasten måles i tema-generatoren, som nekter å skrive tokens under kravet.
Skjermbilde
Opt-in per innsending, tatt først når innsenderen trykker, forhåndsvist og
maskert på enheten før det lastes opp. Pakken tar ingen avhengighet på
fangst — du sender inn captureRef fra
react-native-view-shot:
import { captureRef } from 'react-native-view-shot';
import { createScreenshotAdapter, InnspelPrivate } from '@innspel/react-native';
<InnspelProvider client={innspel} screenshot={createScreenshotAdapter(captureRef)}>Bilde fra galleriet. Et innspill handler sjelden om skjermen widgeten åpnes fra — åpnes den fra en profilside, viser fangsten profilen, ikke feilen. Gi adapteren din egen bildevelger, så får innsenderen «Velg bilde» og kan legge ved et systemskjermbilde tatt der feilen er. Pakken tar ingen avhengighet på velgeren:
import * as ImagePicker from 'expo-image-picker';
import { createScreenshotAdapter, type PickImageFn } from '@innspel/react-native';
const pickImage: PickImageFn = async () => {
const r = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], exif: false });
return r.canceled ? null : { uri: r.assets[0]!.uri, width: r.assets[0]!.width, height: r.assets[0]!.height };
};
// Begge veiene:
<InnspelProvider client={innspel} screenshot={createScreenshotAdapter(captureRef, { pickImage })}>
// Bare «Velg bilde» — skjermen bak arket fanges aldri:
<InnspelProvider client={innspel} screenshot={createScreenshotAdapter(captureRef, { pickImage, captureScreen: false })}>captureRef trengs fortsatt: forhåndsvisningen med maskene fanges på nytt, og det er
komposittet som lastes opp — aldri fila fra galleriet. Systemets bildevelger trenger ingen
tillatelse til biblioteket på Android 13+ og iOS. <InnspelPrivate> kan ikke gjelde et
bilde fra et annet sted, og forhåndsvisningen sier det til innsenderen.
Din egen setning ved bildesteget. Du kjenner brukerne dine og hva som er greit å legge ved; plattformen eier ikke den setningen. Den står ved knappene og i forhåndsvisningen:
<InnspelProvider
client={innspel}
screenshot={adapter}
imageStepNote={{
feil: 'Legg ved et skjermbilde av feilen. Ikke send bilder av andre personer.',
onske: 'Vis gjerne hvor i appen du savner det.',
}}
>En streng gjelder begge spor. Et spor uten setning viser ingen.
Uten screenshot finnes ikke steget — ikke som en grå knapp, det finnes ikke.
Det samme gjelder når leietakeren har skjermbilde av (screenshotsEnabled: false,
tvunget ved mindreårige).
Merk det som aldri skal med på bildet:
<InnspelPrivate><Text>{bruker.navn}</Text></InnspelPrivate>Det maskeres FØR innsenderen ser forhåndsvisningen, merket «Maskert av appen», og penselen kan ikke oppheve det. Din kode er sannheten, ikke brukerens presisjon.
Innsenderen har tre veier selv, alltid: Masker felt (ett trykk legger en boks), Tegn over (fingerstrøk) og Masker all tekst (hele bildet gjøres uskarpt — JavaScript kan ikke finne tekstnoder i din native app, så det er slik «all tekst» ser ut i React Native). Egne masker angres med ett trykk på dem. «Fjern bildet» finnes alltid.
Det som lastes opp er forhåndsvisningen med maskene, fanget på nytt — det umaskerte bildet forlater aldri enheten. Teksten sendes først; feiler bildet, står saken uten det, og kvitteringen sier «Sendt — skjermbildet kunne ikke legges ved». Uten nett køes teksten og bildet forkastes.
Plattformen re-enkoder bildet i en isolert prosess og fjerner EXIF. PNG, JPEG, WebP; høyst 5 MB og 4096 × 4096.
Betaprogram
import { InnspelProgram } from '@innspel/react-native';
<InnspelProgram invitationToken={tokenFraDyplenke} />Sett den der invitasjonen hører hjemme — typisk øverst på hjemskjermen. Uten invitasjon og uten deltakelse rendrer den ingenting.
Den viser invitasjonsbanneret, aksepten med tallene (N oppgaver × M minutter, dato til dato, maks K kontakter), oppgavene, kjente feil du slipper å melde, og «Gå ut av programmet» med bekreftelse. Utgangen er umiddelbar.
Tokenet kommer fra deg. Plattformen sender aldri invitasjonen: du får
program.invitation_created som webhook med engangs-tokenet og pseudonymet,
løser pseudonymet til din egen bruker, og sender lenken i din egen kanal. Fanger
appen tokenet fra dyplenken og gir det hit, kan testeren bli med. Uten token
vises invitasjonen fortsatt, men «Bli med» er inaktiv.
Oppgavene med utfall og «Hvordan gikk det?» kommer i neste versjon.
Versjoner
Semver fra 0.1.0. Før 1.0.0 kan en minor bryte — pin en eksakt versjon
om du trenger ro.
Lisens
MIT. Klientpakkene er permissivt lisensiert fordi koden kjører hos deg og må kunne leses og granskes der. Plattformen bak — API, panel og drift — er det ikke.
