@kernaq/verify-react-native 2.0.0 → 2.0.2
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/README.md +1 -1
- package/dist/index.d.ts +95 -50
- package/dist/index.esm.js +1171 -580
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1169 -576
- package/dist/index.js.map +1 -1
- package/package.json +2 -9
- package/src/KernaqVerify.tsx +172 -176
- package/src/index.ts +9 -4
- package/src/locale.ts +32 -29
- package/src/quality.ts +180 -0
- package/src/steps/DocumentStep.tsx +214 -77
- package/src/steps/IntroStep.tsx +58 -37
- package/src/steps/LivenessStep.tsx +225 -125
- package/src/steps/ProcessingStep.tsx +10 -34
- package/src/steps/ResultStep.tsx +99 -35
- package/src/steps/SelfieStep.tsx +165 -96
- package/src/theme.ts +22 -14
- package/src/types.ts +27 -51
package/src/steps/ResultStep.tsx
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResultStep — no emoji, Unicode geometric icons, detail rows matching web v2.
|
|
3
|
+
*/
|
|
1
4
|
import React from 'react'
|
|
2
5
|
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
|
|
3
6
|
import type { ResolvedTheme } from '../theme'
|
|
4
7
|
import type { VerifyLocale, VerifyResult, VerifyVerdict } from '../types'
|
|
5
8
|
|
|
6
9
|
interface Props {
|
|
7
|
-
theme:
|
|
8
|
-
locale:
|
|
9
|
-
result:
|
|
10
|
-
error:
|
|
10
|
+
theme: ResolvedTheme
|
|
11
|
+
locale: VerifyLocale
|
|
12
|
+
result: VerifyResult | null
|
|
13
|
+
error: string | null
|
|
11
14
|
onRetry: () => void
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
// Unicode block characters — no emoji, render cleanly on all platforms
|
|
18
|
+
const ICON: Record<VerifyVerdict, string> = {
|
|
19
|
+
pass: '\u2714', // ✔ heavy check mark
|
|
20
|
+
fail: '\u2718', // ✘ heavy ballot X
|
|
21
|
+
review: '\u29D6', // ⧖ hourglass
|
|
18
22
|
}
|
|
19
|
-
|
|
20
23
|
const ICON_BG: Record<VerifyVerdict, string> = {
|
|
21
|
-
pass: '#
|
|
24
|
+
pass: '#dcfce7',
|
|
22
25
|
fail: '#fee2e2',
|
|
23
|
-
review: '#
|
|
26
|
+
review: '#fef9c3',
|
|
27
|
+
}
|
|
28
|
+
const ICON_COLOR: Record<VerifyVerdict, string> = {
|
|
29
|
+
pass: '#16a34a',
|
|
30
|
+
fail: '#dc2626',
|
|
31
|
+
review: '#ca8a04',
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
export function ResultStep({ theme, locale, result, error, onRetry }: Props) {
|
|
@@ -29,18 +37,19 @@ export function ResultStep({ theme, locale, result, error, onRetry }: Props) {
|
|
|
29
37
|
if (!result) {
|
|
30
38
|
return (
|
|
31
39
|
<View style={s.container}>
|
|
32
|
-
<View style={[s.
|
|
33
|
-
<Text style={s.icon}
|
|
40
|
+
<View style={[s.iconBox, { backgroundColor: ICON_BG.fail }]}>
|
|
41
|
+
<Text style={[s.icon, { color: ICON_COLOR.fail }]}>{ICON.fail}</Text>
|
|
34
42
|
</View>
|
|
35
43
|
<Text style={s.title}>{locale.result_fail_title}</Text>
|
|
36
44
|
<Text style={s.body}>{error ?? locale.result_fail_body}</Text>
|
|
37
45
|
<TouchableOpacity style={s.btn} onPress={onRetry} activeOpacity={0.85}>
|
|
38
|
-
<Text style={s.btnText}>
|
|
46
|
+
<Text style={s.btnText}>Try again</Text>
|
|
39
47
|
</TouchableOpacity>
|
|
40
48
|
</View>
|
|
41
49
|
)
|
|
42
50
|
}
|
|
43
51
|
|
|
52
|
+
const v = result.verdict
|
|
44
53
|
const titles: Record<VerifyVerdict, string> = {
|
|
45
54
|
pass: locale.result_pass_title,
|
|
46
55
|
fail: locale.result_fail_title,
|
|
@@ -52,16 +61,37 @@ export function ResultStep({ theme, locale, result, error, onRetry }: Props) {
|
|
|
52
61
|
review: locale.result_review_body,
|
|
53
62
|
}
|
|
54
63
|
|
|
64
|
+
const rows: Array<{ label: string; value: string; ok?: boolean; bad?: boolean }> = [
|
|
65
|
+
{ label: 'Verdict', value: v.charAt(0).toUpperCase() + v.slice(1), ok: v === 'pass', bad: v === 'fail' },
|
|
66
|
+
{ label: 'Risk score', value: String(result.score ?? '—') },
|
|
67
|
+
{ label: 'Face match', value: result.faceMatch ? 'Confirmed' : 'Not confirmed', ok: result.faceMatch, bad: !result.faceMatch },
|
|
68
|
+
{ label: 'Liveness', value: result.isLive ? 'Confirmed' : 'Not confirmed', ok: result.isLive, bad: !result.isLive },
|
|
69
|
+
]
|
|
70
|
+
|
|
55
71
|
return (
|
|
56
72
|
<View style={s.container}>
|
|
57
|
-
<View style={[s.
|
|
58
|
-
<Text style={s.icon}>{
|
|
73
|
+
<View style={[s.iconBox, { backgroundColor: ICON_BG[v] }]}>
|
|
74
|
+
<Text style={[s.icon, { color: ICON_COLOR[v] }]}>{ICON[v]}</Text>
|
|
75
|
+
</View>
|
|
76
|
+
<Text style={s.title}>{titles[v]}</Text>
|
|
77
|
+
<Text style={s.body}>{bodies[v]}</Text>
|
|
78
|
+
|
|
79
|
+
<View style={s.details}>
|
|
80
|
+
{rows.map((row) => (
|
|
81
|
+
<View key={row.label} style={s.row}>
|
|
82
|
+
<Text style={s.rowLabel}>{row.label}</Text>
|
|
83
|
+
<Text style={[
|
|
84
|
+
s.rowVal,
|
|
85
|
+
row.ok && s.rowOk,
|
|
86
|
+
row.bad && s.rowBad,
|
|
87
|
+
]}>{row.value}</Text>
|
|
88
|
+
</View>
|
|
89
|
+
))}
|
|
59
90
|
</View>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
{result.verdict === 'fail' && (
|
|
91
|
+
|
|
92
|
+
{v === 'fail' && (
|
|
63
93
|
<TouchableOpacity style={s.btn} onPress={onRetry} activeOpacity={0.85}>
|
|
64
|
-
<Text style={s.btnText}>
|
|
94
|
+
<Text style={s.btnText}>Try again</Text>
|
|
65
95
|
</TouchableOpacity>
|
|
66
96
|
)}
|
|
67
97
|
</View>
|
|
@@ -71,38 +101,72 @@ export function ResultStep({ theme, locale, result, error, onRetry }: Props) {
|
|
|
71
101
|
const styles = (t: ResolvedTheme) =>
|
|
72
102
|
StyleSheet.create({
|
|
73
103
|
container: {
|
|
74
|
-
|
|
104
|
+
flex: 1,
|
|
105
|
+
padding: 28,
|
|
75
106
|
alignItems: 'center',
|
|
76
|
-
gap:
|
|
107
|
+
gap: 14,
|
|
108
|
+
justifyContent: 'center',
|
|
77
109
|
},
|
|
78
|
-
|
|
79
|
-
width:
|
|
80
|
-
height:
|
|
81
|
-
borderRadius:
|
|
110
|
+
iconBox: {
|
|
111
|
+
width: 52,
|
|
112
|
+
height: 52,
|
|
113
|
+
borderRadius: 14,
|
|
82
114
|
alignItems: 'center',
|
|
83
115
|
justifyContent: 'center',
|
|
84
|
-
marginBottom:
|
|
116
|
+
marginBottom: 4,
|
|
117
|
+
},
|
|
118
|
+
icon: {
|
|
119
|
+
fontSize: 22,
|
|
120
|
+
fontWeight: '700',
|
|
85
121
|
},
|
|
86
|
-
icon: { fontSize: 34 },
|
|
87
122
|
title: {
|
|
88
123
|
fontSize: 20,
|
|
89
124
|
fontWeight: '700',
|
|
90
125
|
color: t.text,
|
|
91
126
|
textAlign: 'center',
|
|
127
|
+
letterSpacing: -0.3,
|
|
92
128
|
},
|
|
93
129
|
body: {
|
|
94
|
-
fontSize:
|
|
130
|
+
fontSize: 13.5,
|
|
95
131
|
color: t.subtext,
|
|
96
132
|
textAlign: 'center',
|
|
97
|
-
lineHeight:
|
|
133
|
+
lineHeight: 19,
|
|
98
134
|
},
|
|
135
|
+
details: {
|
|
136
|
+
width: '100%',
|
|
137
|
+
gap: 6,
|
|
138
|
+
},
|
|
139
|
+
row: {
|
|
140
|
+
flexDirection: 'row',
|
|
141
|
+
justifyContent: 'space-between',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
paddingVertical: 9,
|
|
144
|
+
paddingHorizontal: 12,
|
|
145
|
+
backgroundColor: t.surface,
|
|
146
|
+
borderRadius: 8,
|
|
147
|
+
},
|
|
148
|
+
rowLabel: {
|
|
149
|
+
fontSize: 12.5,
|
|
150
|
+
color: t.subtext,
|
|
151
|
+
},
|
|
152
|
+
rowVal: {
|
|
153
|
+
fontSize: 12.5,
|
|
154
|
+
fontWeight: '600',
|
|
155
|
+
color: t.text,
|
|
156
|
+
},
|
|
157
|
+
rowOk: { color: '#16a34a' },
|
|
158
|
+
rowBad: { color: '#dc2626' },
|
|
99
159
|
btn: {
|
|
100
|
-
|
|
160
|
+
width: '100%',
|
|
101
161
|
backgroundColor: t.accent,
|
|
102
|
-
borderRadius: t.radius,
|
|
103
|
-
paddingVertical:
|
|
104
|
-
paddingHorizontal: 32,
|
|
162
|
+
borderRadius: t.radius - 4,
|
|
163
|
+
paddingVertical: 12,
|
|
105
164
|
alignItems: 'center',
|
|
165
|
+
marginTop: 4,
|
|
166
|
+
},
|
|
167
|
+
btnText: {
|
|
168
|
+
color: t.accentInv,
|
|
169
|
+
fontSize: 14,
|
|
170
|
+
fontWeight: '600',
|
|
106
171
|
},
|
|
107
|
-
btnText: { color: '#fff', fontSize: 15, fontWeight: '600' },
|
|
108
172
|
})
|
package/src/steps/SelfieStep.tsx
CHANGED
|
@@ -1,82 +1,136 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* SelfieStep — auto-captures when face quality passes.
|
|
3
|
+
*
|
|
4
|
+
* Every 120ms:
|
|
5
|
+
* 1. takeSnapshot (quality 20) — tiny file, fast
|
|
6
|
+
* 2. analyseSelfieFrame() — brightness, blur, glare checks (mirrors web SDK)
|
|
7
|
+
* 3. Shows live guidance pill with the first failing check message
|
|
8
|
+
* 4. After HOLD_FRAMES consecutive passing frames (~1.5s) → takePhoto (full quality)
|
|
9
|
+
*/
|
|
10
|
+
import React, { useEffect, useRef, useState } from 'react'
|
|
2
11
|
import {
|
|
3
12
|
View,
|
|
4
13
|
Text,
|
|
5
|
-
TouchableOpacity,
|
|
6
|
-
Image,
|
|
7
14
|
StyleSheet,
|
|
8
15
|
ActivityIndicator,
|
|
16
|
+
Animated,
|
|
9
17
|
} from 'react-native'
|
|
10
|
-
import { Camera } from 'react-native-vision-camera'
|
|
11
|
-
import {
|
|
18
|
+
import { Camera, useCameraDevice } from 'react-native-vision-camera'
|
|
19
|
+
import { analyseSelfieFrame } from '../quality'
|
|
12
20
|
import type { ResolvedTheme } from '../theme'
|
|
13
21
|
import type { VerifyLocale } from '../types'
|
|
14
22
|
|
|
23
|
+
const HOLD_FRAMES = 12 // ~1.5s at 120ms per frame
|
|
24
|
+
|
|
15
25
|
interface Props {
|
|
16
|
-
theme:
|
|
17
|
-
locale:
|
|
26
|
+
theme: ResolvedTheme
|
|
27
|
+
locale: VerifyLocale
|
|
18
28
|
onCapture: (uri: string) => void
|
|
19
|
-
onBack: () => void
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
export function SelfieStep({ theme, locale, onCapture
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const [
|
|
31
|
+
export function SelfieStep({ theme, locale, onCapture }: Props) {
|
|
32
|
+
const cameraRef = useRef<Camera>(null)
|
|
33
|
+
const device = useCameraDevice('front')
|
|
34
|
+
const [pill, setPill] = useState('Align your face')
|
|
35
|
+
const [pillOk, setPillOk] = useState(false)
|
|
36
|
+
const [capturing, setCapturing] = useState(false)
|
|
37
|
+
const readyFrames = useRef(0)
|
|
38
|
+
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
|
39
|
+
const borderAnim = useRef(new Animated.Value(0)).current // 0 = white, 1 = green
|
|
26
40
|
const s = styles(theme)
|
|
27
41
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
// Animate oval border colour
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
Animated.timing(borderAnim, {
|
|
45
|
+
toValue: pillOk ? 1 : 0,
|
|
46
|
+
duration: 200,
|
|
47
|
+
useNativeDriver: false,
|
|
48
|
+
}).start()
|
|
49
|
+
}, [pillOk, borderAnim])
|
|
50
|
+
|
|
51
|
+
const ovalBorder = borderAnim.interpolate({
|
|
52
|
+
inputRange: [0, 1],
|
|
53
|
+
outputRange: ['rgba(255,255,255,0.75)', '#4ade80'],
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!device || capturing) return
|
|
58
|
+
|
|
59
|
+
timerRef.current = setInterval(async () => {
|
|
60
|
+
if (!cameraRef.current || capturing) return
|
|
61
|
+
try {
|
|
62
|
+
// Fast low-quality snapshot for analysis only
|
|
63
|
+
const snap = await cameraRef.current.takeSnapshot({ quality: 20 })
|
|
64
|
+
const uri = `file://${snap.path}`
|
|
65
|
+
|
|
66
|
+
const { passing, message } = await analyseSelfieFrame(uri, {
|
|
67
|
+
minBlurScore: 22,
|
|
68
|
+
minBrightness: 40,
|
|
69
|
+
maxBrightness: 220,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
setPill(passing ? 'Hold still\u2026' : message)
|
|
73
|
+
setPillOk(passing)
|
|
74
|
+
|
|
75
|
+
if (passing) {
|
|
76
|
+
readyFrames.current++
|
|
77
|
+
} else {
|
|
78
|
+
readyFrames.current = 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (readyFrames.current >= HOLD_FRAMES) {
|
|
82
|
+
// Stop loop, take full-quality photo
|
|
83
|
+
clearInterval(timerRef.current!)
|
|
84
|
+
timerRef.current = null
|
|
85
|
+
setCapturing(true)
|
|
86
|
+
const full = await cameraRef.current.takePhoto({ qualityPrioritization: 'quality' })
|
|
87
|
+
onCapture(`file://${full.path}`)
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
readyFrames.current = 0
|
|
91
|
+
}
|
|
92
|
+
}, 120)
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
if (timerRef.current) clearInterval(timerRef.current)
|
|
96
|
+
}
|
|
97
|
+
}, [device, capturing, onCapture])
|
|
34
98
|
|
|
35
99
|
if (!device) return <ActivityIndicator color={theme.accent} style={{ margin: 40 }} />
|
|
36
100
|
|
|
37
101
|
return (
|
|
38
102
|
<View style={s.container}>
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
>
|
|
71
|
-
{capturing
|
|
72
|
-
? <ActivityIndicator color="#fff" />
|
|
73
|
-
: <Text style={s.btnText}>{locale.selfie_capture_btn}</Text>
|
|
74
|
-
}
|
|
75
|
-
</TouchableOpacity>
|
|
76
|
-
<TouchableOpacity style={s.btnSecondary} onPress={onBack} activeOpacity={0.75}>
|
|
77
|
-
<Text style={s.btnSecondaryText}>{locale.doc_retake_btn}</Text>
|
|
78
|
-
</TouchableOpacity>
|
|
79
|
-
</>
|
|
103
|
+
<View style={s.heading}>
|
|
104
|
+
<Text style={s.title}>{locale.selfie_title}</Text>
|
|
105
|
+
<Text style={s.subtitle}>{locale.selfie_instruction}</Text>
|
|
106
|
+
</View>
|
|
107
|
+
|
|
108
|
+
<View style={s.viewport}>
|
|
109
|
+
<Camera
|
|
110
|
+
ref={cameraRef}
|
|
111
|
+
style={StyleSheet.absoluteFill}
|
|
112
|
+
device={device}
|
|
113
|
+
isActive={!capturing}
|
|
114
|
+
photo
|
|
115
|
+
/>
|
|
116
|
+
|
|
117
|
+
{/* Oval guide with animated border */}
|
|
118
|
+
<Animated.View
|
|
119
|
+
style={[s.oval, { borderColor: ovalBorder }]}
|
|
120
|
+
pointerEvents="none"
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
{/* Live guidance pill */}
|
|
124
|
+
<View style={[s.pill, pillOk && s.pillOk]} pointerEvents="none">
|
|
125
|
+
<Text style={s.pillText}>{pill}</Text>
|
|
126
|
+
</View>
|
|
127
|
+
</View>
|
|
128
|
+
|
|
129
|
+
{capturing && (
|
|
130
|
+
<View style={s.capturingRow}>
|
|
131
|
+
<ActivityIndicator color={theme.accent} size="small" />
|
|
132
|
+
<Text style={s.capturingText}>Capturing…</Text>
|
|
133
|
+
</View>
|
|
80
134
|
)}
|
|
81
135
|
</View>
|
|
82
136
|
)
|
|
@@ -84,55 +138,70 @@ export function SelfieStep({ theme, locale, onCapture, onBack }: Props) {
|
|
|
84
138
|
|
|
85
139
|
const styles = (t: ResolvedTheme) =>
|
|
86
140
|
StyleSheet.create({
|
|
87
|
-
container: {
|
|
88
|
-
|
|
89
|
-
|
|
141
|
+
container: {
|
|
142
|
+
flex: 1,
|
|
143
|
+
padding: 20,
|
|
144
|
+
gap: 12,
|
|
145
|
+
},
|
|
146
|
+
heading: { gap: 4, alignItems: 'center' },
|
|
147
|
+
title: {
|
|
148
|
+
fontSize: 20,
|
|
149
|
+
fontWeight: '700',
|
|
150
|
+
color: t.text,
|
|
151
|
+
textAlign: 'center',
|
|
152
|
+
letterSpacing: -0.3,
|
|
153
|
+
},
|
|
154
|
+
subtitle: {
|
|
155
|
+
fontSize: 13,
|
|
156
|
+
color: t.subtext,
|
|
157
|
+
lineHeight: 19,
|
|
158
|
+
textAlign: 'center',
|
|
159
|
+
},
|
|
90
160
|
viewport: {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
borderRadius: t.radius,
|
|
161
|
+
flex: 1,
|
|
162
|
+
borderRadius: t.radius - 4,
|
|
94
163
|
overflow: 'hidden',
|
|
95
164
|
backgroundColor: '#000',
|
|
96
|
-
marginBottom: 16,
|
|
97
165
|
position: 'relative',
|
|
98
|
-
alignSelf: 'center',
|
|
99
|
-
maxWidth: 280,
|
|
100
166
|
},
|
|
101
|
-
|
|
167
|
+
oval: {
|
|
102
168
|
position: 'absolute',
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
bottom: '
|
|
169
|
+
left: '21%',
|
|
170
|
+
right: '21%',
|
|
171
|
+
top: '6%',
|
|
172
|
+
bottom: '6%',
|
|
107
173
|
borderWidth: 2,
|
|
108
|
-
borderColor: 'rgba(255,255,255,0.7)',
|
|
109
174
|
borderRadius: 999,
|
|
110
175
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
borderRadius: t.radius,
|
|
115
|
-
marginBottom: 16,
|
|
176
|
+
pill: {
|
|
177
|
+
position: 'absolute',
|
|
178
|
+
bottom: 14,
|
|
116
179
|
alignSelf: 'center',
|
|
117
|
-
|
|
118
|
-
|
|
180
|
+
backgroundColor: 'rgba(0,0,0,0.65)',
|
|
181
|
+
borderRadius: 999,
|
|
182
|
+
paddingHorizontal: 16,
|
|
183
|
+
paddingVertical: 7,
|
|
184
|
+
borderWidth: 1.5,
|
|
185
|
+
borderColor: 'rgba(255,255,255,0.15)',
|
|
119
186
|
},
|
|
120
|
-
|
|
121
|
-
backgroundColor:
|
|
122
|
-
|
|
123
|
-
paddingVertical: 14,
|
|
124
|
-
alignItems: 'center',
|
|
125
|
-
marginBottom: 10,
|
|
187
|
+
pillOk: {
|
|
188
|
+
backgroundColor: 'rgba(22,163,74,0.8)',
|
|
189
|
+
borderColor: 'rgba(134,239,172,0.5)',
|
|
126
190
|
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
191
|
+
pillText: {
|
|
192
|
+
color: '#fff',
|
|
193
|
+
fontSize: 13,
|
|
194
|
+
fontWeight: '600',
|
|
195
|
+
},
|
|
196
|
+
capturingRow: {
|
|
197
|
+
flexDirection: 'row',
|
|
131
198
|
alignItems: 'center',
|
|
132
|
-
|
|
133
|
-
|
|
199
|
+
justifyContent: 'center',
|
|
200
|
+
gap: 8,
|
|
201
|
+
paddingVertical: 8,
|
|
202
|
+
},
|
|
203
|
+
capturingText: {
|
|
204
|
+
fontSize: 13,
|
|
205
|
+
color: t.subtext,
|
|
134
206
|
},
|
|
135
|
-
btnDisabled: { opacity: 0.6 },
|
|
136
|
-
btnText: { color: '#fff', fontSize: 15, fontWeight: '600' },
|
|
137
|
-
btnSecondaryText: { color: t.text, fontSize: 15, fontWeight: '600' },
|
|
138
207
|
})
|
package/src/theme.ts
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
import type { VerifyTheme } from './types'
|
|
2
2
|
|
|
3
3
|
export interface ResolvedTheme {
|
|
4
|
-
accent:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
accent: string
|
|
5
|
+
accentInv: string
|
|
6
|
+
bg: string
|
|
7
|
+
surface: string
|
|
8
|
+
surface2: string
|
|
9
|
+
text: string
|
|
10
|
+
subtext: string
|
|
11
|
+
border: string
|
|
12
|
+
errorBg: string
|
|
13
|
+
errorText: string
|
|
14
|
+
radius: number
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export function resolveTheme(theme?: VerifyTheme): ResolvedTheme {
|
|
14
18
|
const dark = theme?.mode === 'dark'
|
|
15
19
|
return {
|
|
16
|
-
accent:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
accent: theme?.accentColor ?? (dark ? '#e5e7eb' : '#111827'),
|
|
21
|
+
accentInv: dark ? '#111827' : '#ffffff',
|
|
22
|
+
bg: theme?.backgroundColor ?? (dark ? '#0f0f0f' : '#ffffff'),
|
|
23
|
+
surface: theme?.cardColor ?? (dark ? '#1a1a1a' : '#f7f7f7'),
|
|
24
|
+
surface2: dark ? '#242424' : '#efefef',
|
|
25
|
+
text: theme?.textColor ?? (dark ? '#f5f5f5' : '#0f0f0f'),
|
|
26
|
+
subtext: theme?.subtextColor ?? (dark ? '#888888' : '#6b7280'),
|
|
27
|
+
border: theme?.borderColor ?? (dark ? '#2a2a2a' : '#e5e5e5'),
|
|
28
|
+
errorBg: dark ? 'rgba(239,68,68,0.1)' : '#fef2f2',
|
|
29
|
+
errorText: dark ? '#f87171' : '#b91c1c',
|
|
30
|
+
radius: theme?.borderRadius ?? 14,
|
|
23
31
|
}
|
|
24
32
|
}
|