@loopaware/react-native 0.1.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/LICENSE +109 -0
- package/README.md +107 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +206 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
LoopAware Source Available License
|
|
2
|
+
Version 1.0, April 2026
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025-2026 Marco Polo Research Lab
|
|
5
|
+
https://mprlab.com
|
|
6
|
+
|
|
7
|
+
TERMS AND CONDITIONS
|
|
8
|
+
|
|
9
|
+
1. Definitions
|
|
10
|
+
|
|
11
|
+
"Licensor" means Marco Polo Research Lab, the entity offering the
|
|
12
|
+
Software under these terms.
|
|
13
|
+
|
|
14
|
+
"Software" means the LoopAware software made available under this
|
|
15
|
+
license, including all source code, object code, documentation, and
|
|
16
|
+
associated files.
|
|
17
|
+
|
|
18
|
+
"You" means the individual or entity exercising rights under this
|
|
19
|
+
license.
|
|
20
|
+
|
|
21
|
+
"Non-Commercial Use" means use of the Software for any purpose that
|
|
22
|
+
does not generate revenue, directly or indirectly. This includes
|
|
23
|
+
personal projects, open-source projects that are free to use,
|
|
24
|
+
educational use, academic research, and evaluation of the Software.
|
|
25
|
+
|
|
26
|
+
"Commercial Use" means any use of the Software in connection with a
|
|
27
|
+
product, service, or project that generates revenue, directly or
|
|
28
|
+
indirectly. This includes, without limitation: products or services
|
|
29
|
+
sold or licensed for a fee, products or services that generate
|
|
30
|
+
advertising revenue, products or services offered under a paid
|
|
31
|
+
subscription, and internal use by a for-profit entity to support
|
|
32
|
+
revenue-generating operations.
|
|
33
|
+
|
|
34
|
+
"Commercial License" means a separate written agreement between You
|
|
35
|
+
and the Licensor granting rights to Commercial Use.
|
|
36
|
+
|
|
37
|
+
2. Grant of Rights for Non-Commercial Use
|
|
38
|
+
|
|
39
|
+
Subject to the terms of this license, the Licensor grants You a
|
|
40
|
+
non-exclusive, worldwide, royalty-free license to use, copy, modify,
|
|
41
|
+
and create derivative works of the Software, solely for
|
|
42
|
+
Non-Commercial Use.
|
|
43
|
+
|
|
44
|
+
You may redistribute the Software or derivative works, provided that:
|
|
45
|
+
|
|
46
|
+
(a) You include a copy of this license with every distribution;
|
|
47
|
+
(b) You do not remove or alter any copyright notices; and
|
|
48
|
+
(c) You clearly state any modifications You have made.
|
|
49
|
+
|
|
50
|
+
3. Commercial Use Requires a Commercial License
|
|
51
|
+
|
|
52
|
+
You may not use the Software for any Commercial Use unless You have
|
|
53
|
+
obtained a Commercial License from the Licensor.
|
|
54
|
+
|
|
55
|
+
Commercial Licenses are available at:
|
|
56
|
+
https://loopaware.mprlab.com/pricing
|
|
57
|
+
|
|
58
|
+
For licensing inquiries, contact: legal@mprlab.com
|
|
59
|
+
|
|
60
|
+
4. Competing Use Restriction
|
|
61
|
+
|
|
62
|
+
Regardless of whether Your use is commercial or non-commercial, You
|
|
63
|
+
may not use the Software to create, offer, or operate a product or
|
|
64
|
+
service that competes with the Software or any product or service
|
|
65
|
+
offered by the Licensor that incorporates the Software.
|
|
66
|
+
|
|
67
|
+
5. Patents
|
|
68
|
+
|
|
69
|
+
To the extent Your permitted use would necessarily infringe the
|
|
70
|
+
Licensor's patents, this license includes a patent license for that
|
|
71
|
+
use. If You bring a patent claim against any party alleging that the
|
|
72
|
+
Software infringes a patent, Your rights under this license terminate
|
|
73
|
+
immediately.
|
|
74
|
+
|
|
75
|
+
6. Trademarks
|
|
76
|
+
|
|
77
|
+
This license does not grant You any rights to use the Licensor's
|
|
78
|
+
trademarks, trade names, service marks, or product names, except as
|
|
79
|
+
necessary to comply with the attribution requirements in Section 2.
|
|
80
|
+
|
|
81
|
+
7. Disclaimer of Warranty
|
|
82
|
+
|
|
83
|
+
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTIES OF ANY KIND,
|
|
84
|
+
EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF
|
|
85
|
+
FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, TITLE, OR
|
|
86
|
+
NON-INFRINGEMENT.
|
|
87
|
+
|
|
88
|
+
8. Limitation of Liability
|
|
89
|
+
|
|
90
|
+
IN NO EVENT SHALL THE LICENSOR BE LIABLE FOR ANY INDIRECT, SPECIAL,
|
|
91
|
+
INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATED TO
|
|
92
|
+
THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|
93
|
+
THE LICENSOR'S TOTAL LIABILITY UNDER THIS LICENSE SHALL NOT EXCEED
|
|
94
|
+
THE AMOUNTS PAID BY YOU TO THE LICENSOR FOR THE SOFTWARE IN THE
|
|
95
|
+
TWELVE MONTHS PRECEDING THE CLAIM.
|
|
96
|
+
|
|
97
|
+
9. Termination
|
|
98
|
+
|
|
99
|
+
This license terminates automatically if You violate any of its
|
|
100
|
+
terms. Upon termination, You must cease all use of the Software and
|
|
101
|
+
destroy all copies in Your possession.
|
|
102
|
+
|
|
103
|
+
The Licensor may, at its sole discretion, reinstate Your rights under
|
|
104
|
+
this license upon written notice.
|
|
105
|
+
|
|
106
|
+
10. Governing Law
|
|
107
|
+
|
|
108
|
+
This license is governed by the laws of the State of California,
|
|
109
|
+
United States, without regard to conflict of law principles.
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# LoopAware React Native Feedback
|
|
2
|
+
|
|
3
|
+
Native feedback button for Expo and React Native apps. This client submits customer feedback to `/public/mobile-feedback`; it is not part of LA Sentry error capture.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @loopaware/react-native
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package ships built ESM JavaScript and TypeScript declarations from `dist/`. React and React Native are peer
|
|
12
|
+
dependencies and must come from the host app.
|
|
13
|
+
|
|
14
|
+
## Expo setup
|
|
15
|
+
|
|
16
|
+
Register the mobile app for a LoopAware site through the authenticated API:
|
|
17
|
+
|
|
18
|
+
```http
|
|
19
|
+
POST /api/sites/{site_id}/mobile-apps
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"platform": "ios",
|
|
25
|
+
"app_identifier": "com.example.app",
|
|
26
|
+
"display_name": "Example iOS"
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The response includes a public `client_id`. Ship that value with the app alongside the site ID.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import * as Application from "expo-application";
|
|
34
|
+
import { LoopAwareFeedbackButton, LoopAwareProvider } from "@loopaware/react-native";
|
|
35
|
+
|
|
36
|
+
export function App() {
|
|
37
|
+
return (
|
|
38
|
+
<LoopAwareProvider
|
|
39
|
+
siteId="SITE_ID"
|
|
40
|
+
mobileClientId="MOBILE_CLIENT_ID"
|
|
41
|
+
apiOrigin="https://loopaware.mprlab.com"
|
|
42
|
+
app={{
|
|
43
|
+
applicationId: Application.applicationId ?? "com.example.app",
|
|
44
|
+
version: Application.nativeApplicationVersion ?? "",
|
|
45
|
+
build: Application.nativeBuildVersion ?? "",
|
|
46
|
+
environment: "production",
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<CheckoutScreen />
|
|
50
|
+
</LoopAwareProvider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function CheckoutScreen() {
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
<CheckoutContent />
|
|
58
|
+
<LoopAwareFeedbackButton
|
|
59
|
+
screen={{ name: "Checkout", path: "/checkout/payment" }}
|
|
60
|
+
context={{ step: "payment", plan: "pro" }}
|
|
61
|
+
/>
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The `context` object should contain only safe product state. Do not include passwords, payment data, tokens, or free-form private user content.
|
|
68
|
+
|
|
69
|
+
## Manual submission
|
|
70
|
+
|
|
71
|
+
Apps with their own UI can call `submitLoopAwareFeedback` directly:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
await submitLoopAwareFeedback(
|
|
75
|
+
{
|
|
76
|
+
siteId: "SITE_ID",
|
|
77
|
+
mobileClientId: "MOBILE_CLIENT_ID",
|
|
78
|
+
apiOrigin: "https://loopaware.mprlab.com",
|
|
79
|
+
app: {
|
|
80
|
+
platform: "android",
|
|
81
|
+
applicationId: "com.example.app",
|
|
82
|
+
version: "1.2.3",
|
|
83
|
+
build: "44",
|
|
84
|
+
environment: "production",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
contact: "person@example.com",
|
|
89
|
+
message: "The checkout button is confusing.",
|
|
90
|
+
sentiment: "sad",
|
|
91
|
+
screen: { name: "Checkout", path: "/checkout/payment" },
|
|
92
|
+
context: { step: "payment" },
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Non-React Native apps
|
|
98
|
+
|
|
99
|
+
Swift, Kotlin, and other native clients should call the same `/public/mobile-feedback` REST endpoint directly. Register
|
|
100
|
+
the app with `/api/sites/{site_id}/mobile-apps`, store the public `client_id`, and submit the same JSON shape used by
|
|
101
|
+
`submitLoopAwareFeedback`.
|
|
102
|
+
|
|
103
|
+
## Package validation
|
|
104
|
+
|
|
105
|
+
Repository CI runs `make client-react-native-check`. That target typechecks the source, builds `dist/`, packs the npm
|
|
106
|
+
tarball, verifies the package contents, installs the tarball into a temporary consumer project, and typechecks a real
|
|
107
|
+
`@loopaware/react-native` import.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type LoopAwareSentiment = "sad" | "neutral" | "happy";
|
|
3
|
+
export type LoopAwareContextValue = string | number | boolean | null | LoopAwareContextValue[] | {
|
|
4
|
+
[key: string]: LoopAwareContextValue;
|
|
5
|
+
};
|
|
6
|
+
export type LoopAwareFeedbackContext = {
|
|
7
|
+
[key: string]: LoopAwareContextValue;
|
|
8
|
+
};
|
|
9
|
+
export type LoopAwareScreen = {
|
|
10
|
+
name: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
};
|
|
13
|
+
export type LoopAwareAppMetadata = {
|
|
14
|
+
platform?: "ios" | "android";
|
|
15
|
+
applicationId: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
build?: string;
|
|
18
|
+
environment?: string;
|
|
19
|
+
};
|
|
20
|
+
export type LoopAwareConfig = {
|
|
21
|
+
siteId: string;
|
|
22
|
+
mobileClientId: string;
|
|
23
|
+
apiOrigin: string;
|
|
24
|
+
app: LoopAwareAppMetadata;
|
|
25
|
+
defaultContext?: LoopAwareFeedbackContext;
|
|
26
|
+
};
|
|
27
|
+
export type LoopAwareProviderProps = LoopAwareConfig & {
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
};
|
|
30
|
+
export type LoopAwareFeedbackInput = {
|
|
31
|
+
contact: string;
|
|
32
|
+
message: string;
|
|
33
|
+
sentiment?: LoopAwareSentiment;
|
|
34
|
+
screen: LoopAwareScreen;
|
|
35
|
+
context?: LoopAwareFeedbackContext;
|
|
36
|
+
};
|
|
37
|
+
export type LoopAwareFeedbackButtonProps = {
|
|
38
|
+
screen: LoopAwareScreen;
|
|
39
|
+
context?: LoopAwareFeedbackContext;
|
|
40
|
+
buttonLabel?: string;
|
|
41
|
+
};
|
|
42
|
+
export declare function LoopAwareProvider({ children, siteId, mobileClientId, apiOrigin, app, defaultContext, }: LoopAwareProviderProps): JSX.Element;
|
|
43
|
+
export declare function useLoopAware(): LoopAwareConfig;
|
|
44
|
+
export declare function submitLoopAwareFeedback(config: LoopAwareConfig, input: LoopAwareFeedbackInput): Promise<void>;
|
|
45
|
+
export declare function LoopAwareFeedbackButton({ screen, context, buttonLabel }: LoopAwareFeedbackButtonProps): JSX.Element;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo, useState } from "react";
|
|
2
|
+
import { Modal, Platform, Pressable, StyleSheet, Text, TextInput, View, } from "react-native";
|
|
3
|
+
const LoopAwareContext = createContext(null);
|
|
4
|
+
export function LoopAwareProvider({ children, siteId, mobileClientId, apiOrigin, app, defaultContext, }) {
|
|
5
|
+
const value = useMemo(() => ({
|
|
6
|
+
siteId,
|
|
7
|
+
mobileClientId,
|
|
8
|
+
apiOrigin: apiOrigin.replace(/\/+$/, ""),
|
|
9
|
+
app,
|
|
10
|
+
defaultContext,
|
|
11
|
+
}), [siteId, mobileClientId, apiOrigin, app, defaultContext]);
|
|
12
|
+
return React.createElement(LoopAwareContext.Provider, { value: value }, children);
|
|
13
|
+
}
|
|
14
|
+
export function useLoopAware() {
|
|
15
|
+
const config = useContext(LoopAwareContext);
|
|
16
|
+
if (!config) {
|
|
17
|
+
throw new Error("loopaware_provider_missing");
|
|
18
|
+
}
|
|
19
|
+
return config;
|
|
20
|
+
}
|
|
21
|
+
export async function submitLoopAwareFeedback(config, input) {
|
|
22
|
+
const mergedContext = {
|
|
23
|
+
...(config.defaultContext ?? {}),
|
|
24
|
+
...(input.context ?? {}),
|
|
25
|
+
};
|
|
26
|
+
const platform = config.app.platform ?? (Platform.OS === "ios" || Platform.OS === "android" ? Platform.OS : "ios");
|
|
27
|
+
const response = await fetch(`${config.apiOrigin.replace(/\/+$/, "")}/public/mobile-feedback`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "Content-Type": "application/json" },
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
site_id: config.siteId,
|
|
32
|
+
mobile_client_id: config.mobileClientId,
|
|
33
|
+
contact: input.contact,
|
|
34
|
+
message: input.message,
|
|
35
|
+
sentiment: input.sentiment ?? "",
|
|
36
|
+
screen: {
|
|
37
|
+
name: input.screen.name,
|
|
38
|
+
path: input.screen.path ?? "",
|
|
39
|
+
},
|
|
40
|
+
app: {
|
|
41
|
+
platform,
|
|
42
|
+
application_id: config.app.applicationId,
|
|
43
|
+
version: config.app.version ?? "",
|
|
44
|
+
build: config.app.build ?? "",
|
|
45
|
+
environment: config.app.environment ?? "",
|
|
46
|
+
},
|
|
47
|
+
context: mergedContext,
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(`loopaware_feedback_failed:${response.status}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function LoopAwareFeedbackButton({ screen, context, buttonLabel = "Feedback" }) {
|
|
55
|
+
const config = useLoopAware();
|
|
56
|
+
const [open, setOpen] = useState(false);
|
|
57
|
+
const [contact, setContact] = useState("");
|
|
58
|
+
const [message, setMessage] = useState("");
|
|
59
|
+
const [sentiment, setSentiment] = useState();
|
|
60
|
+
const [status, setStatus] = useState("");
|
|
61
|
+
const [submitting, setSubmitting] = useState(false);
|
|
62
|
+
async function handleSubmit() {
|
|
63
|
+
const normalizedContact = contact.trim();
|
|
64
|
+
const normalizedMessage = message.trim();
|
|
65
|
+
if (!normalizedContact || (!normalizedMessage && !sentiment)) {
|
|
66
|
+
setStatus("Add contact and feedback.");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
setSubmitting(true);
|
|
70
|
+
setStatus("Sending...");
|
|
71
|
+
try {
|
|
72
|
+
await submitLoopAwareFeedback(config, {
|
|
73
|
+
contact: normalizedContact,
|
|
74
|
+
message: normalizedMessage,
|
|
75
|
+
sentiment,
|
|
76
|
+
screen,
|
|
77
|
+
context,
|
|
78
|
+
});
|
|
79
|
+
setContact("");
|
|
80
|
+
setMessage("");
|
|
81
|
+
setSentiment(undefined);
|
|
82
|
+
setStatus("Sent.");
|
|
83
|
+
setOpen(false);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
setStatus("Unable to send feedback.");
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
setSubmitting(false);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return (React.createElement(React.Fragment, null,
|
|
93
|
+
React.createElement(Pressable, { accessibilityRole: "button", onPress: () => setOpen(true), style: styles.floatingButton },
|
|
94
|
+
React.createElement(Text, { style: styles.floatingButtonText }, buttonLabel)),
|
|
95
|
+
React.createElement(Modal, { animationType: "slide", transparent: true, visible: open, onRequestClose: () => setOpen(false) },
|
|
96
|
+
React.createElement(View, { style: styles.modalBackdrop },
|
|
97
|
+
React.createElement(View, { style: styles.sheet },
|
|
98
|
+
React.createElement(Text, { style: styles.title }, "Send feedback"),
|
|
99
|
+
React.createElement(TextInput, { autoCapitalize: "none", keyboardType: "email-address", onChangeText: setContact, placeholder: "Email or phone", style: styles.input, value: contact }),
|
|
100
|
+
React.createElement(View, { style: styles.sentimentRow }, ["sad", "neutral", "happy"].map((value) => (React.createElement(Pressable, { accessibilityRole: "button", key: value, onPress: () => setSentiment(value), style: [styles.sentimentButton, sentiment === value ? styles.sentimentButtonActive : null] },
|
|
101
|
+
React.createElement(Text, { style: styles.sentimentText }, value))))),
|
|
102
|
+
React.createElement(TextInput, { multiline: true, onChangeText: setMessage, placeholder: "Your message", style: [styles.input, styles.messageInput], value: message }),
|
|
103
|
+
status ? React.createElement(Text, { style: styles.status }, status) : null,
|
|
104
|
+
React.createElement(View, { style: styles.actions },
|
|
105
|
+
React.createElement(Pressable, { accessibilityRole: "button", onPress: () => setOpen(false), style: styles.secondaryButton },
|
|
106
|
+
React.createElement(Text, { style: styles.secondaryButtonText }, "Cancel")),
|
|
107
|
+
React.createElement(Pressable, { accessibilityRole: "button", disabled: submitting, onPress: handleSubmit, style: [styles.primaryButton, submitting ? styles.disabledButton : null] },
|
|
108
|
+
React.createElement(Text, { style: styles.primaryButtonText }, "Send"))))))));
|
|
109
|
+
}
|
|
110
|
+
const styles = StyleSheet.create({
|
|
111
|
+
actions: {
|
|
112
|
+
flexDirection: "row",
|
|
113
|
+
gap: 12,
|
|
114
|
+
justifyContent: "flex-end",
|
|
115
|
+
},
|
|
116
|
+
disabledButton: {
|
|
117
|
+
opacity: 0.6,
|
|
118
|
+
},
|
|
119
|
+
floatingButton: {
|
|
120
|
+
alignItems: "center",
|
|
121
|
+
backgroundColor: "#0d6efd",
|
|
122
|
+
borderRadius: 24,
|
|
123
|
+
bottom: 24,
|
|
124
|
+
elevation: 4,
|
|
125
|
+
paddingHorizontal: 18,
|
|
126
|
+
paddingVertical: 12,
|
|
127
|
+
position: "absolute",
|
|
128
|
+
right: 20,
|
|
129
|
+
},
|
|
130
|
+
floatingButtonText: {
|
|
131
|
+
color: "#ffffff",
|
|
132
|
+
fontWeight: "700",
|
|
133
|
+
},
|
|
134
|
+
input: {
|
|
135
|
+
borderColor: "#ced4da",
|
|
136
|
+
borderRadius: 8,
|
|
137
|
+
borderWidth: 1,
|
|
138
|
+
marginBottom: 12,
|
|
139
|
+
paddingHorizontal: 12,
|
|
140
|
+
paddingVertical: 10,
|
|
141
|
+
},
|
|
142
|
+
messageInput: {
|
|
143
|
+
minHeight: 110,
|
|
144
|
+
textAlignVertical: "top",
|
|
145
|
+
},
|
|
146
|
+
modalBackdrop: {
|
|
147
|
+
backgroundColor: "rgba(0, 0, 0, 0.35)",
|
|
148
|
+
flex: 1,
|
|
149
|
+
justifyContent: "flex-end",
|
|
150
|
+
},
|
|
151
|
+
primaryButton: {
|
|
152
|
+
backgroundColor: "#0d6efd",
|
|
153
|
+
borderRadius: 8,
|
|
154
|
+
paddingHorizontal: 16,
|
|
155
|
+
paddingVertical: 10,
|
|
156
|
+
},
|
|
157
|
+
primaryButtonText: {
|
|
158
|
+
color: "#ffffff",
|
|
159
|
+
fontWeight: "700",
|
|
160
|
+
},
|
|
161
|
+
secondaryButton: {
|
|
162
|
+
borderColor: "#6c757d",
|
|
163
|
+
borderRadius: 8,
|
|
164
|
+
borderWidth: 1,
|
|
165
|
+
paddingHorizontal: 16,
|
|
166
|
+
paddingVertical: 10,
|
|
167
|
+
},
|
|
168
|
+
secondaryButtonText: {
|
|
169
|
+
color: "#343a40",
|
|
170
|
+
fontWeight: "700",
|
|
171
|
+
},
|
|
172
|
+
sentimentButton: {
|
|
173
|
+
borderColor: "#ced4da",
|
|
174
|
+
borderRadius: 8,
|
|
175
|
+
borderWidth: 1,
|
|
176
|
+
paddingHorizontal: 12,
|
|
177
|
+
paddingVertical: 8,
|
|
178
|
+
},
|
|
179
|
+
sentimentButtonActive: {
|
|
180
|
+
backgroundColor: "#e7f1ff",
|
|
181
|
+
borderColor: "#0d6efd",
|
|
182
|
+
},
|
|
183
|
+
sentimentRow: {
|
|
184
|
+
flexDirection: "row",
|
|
185
|
+
gap: 8,
|
|
186
|
+
marginBottom: 12,
|
|
187
|
+
},
|
|
188
|
+
sentimentText: {
|
|
189
|
+
textTransform: "capitalize",
|
|
190
|
+
},
|
|
191
|
+
sheet: {
|
|
192
|
+
backgroundColor: "#ffffff",
|
|
193
|
+
borderTopLeftRadius: 16,
|
|
194
|
+
borderTopRightRadius: 16,
|
|
195
|
+
padding: 20,
|
|
196
|
+
},
|
|
197
|
+
status: {
|
|
198
|
+
color: "#495057",
|
|
199
|
+
marginBottom: 12,
|
|
200
|
+
},
|
|
201
|
+
title: {
|
|
202
|
+
fontSize: 18,
|
|
203
|
+
fontWeight: "700",
|
|
204
|
+
marginBottom: 16,
|
|
205
|
+
},
|
|
206
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@loopaware/react-native",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React Native feedback widget client for LoopAware.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"react-native": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/tyemirov/loopaware.git",
|
|
25
|
+
"directory": "clients/react-native"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"loopaware",
|
|
29
|
+
"feedback",
|
|
30
|
+
"react-native",
|
|
31
|
+
"expo"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"registry": "https://registry.npmjs.org/"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "node scripts/clean-dist.mjs && tsc -p tsconfig.build.json",
|
|
39
|
+
"prepack": "npm run build",
|
|
40
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
41
|
+
"verify-package": "node scripts/verify-package.mjs"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": ">=18.0.0",
|
|
45
|
+
"react-native": ">=0.72.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"typescript": "^5.4.5"
|
|
49
|
+
}
|
|
50
|
+
}
|