react-native-rectangle-doc-scanner 3.92.0 → 3.93.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/dist/DocScanner.js +31 -1
- package/dist/FullDocScanner.js +59 -42
- package/package.json +1 -1
- package/src/DocScanner.tsx +33 -1
- package/src/FullDocScanner.tsx +59 -48
package/dist/DocScanner.js
CHANGED
|
@@ -76,11 +76,20 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
|
|
|
76
76
|
const [detectedRectangle, setDetectedRectangle] = (0, react_1.useState)(null);
|
|
77
77
|
const lastRectangleRef = (0, react_1.useRef)(null);
|
|
78
78
|
const captureOriginRef = (0, react_1.useRef)('auto');
|
|
79
|
+
const rectangleClearTimeoutRef = (0, react_1.useRef)(null);
|
|
79
80
|
(0, react_1.useEffect)(() => {
|
|
80
81
|
if (!autoCapture) {
|
|
81
82
|
setIsAutoCapturing(false);
|
|
82
83
|
}
|
|
83
84
|
}, [autoCapture]);
|
|
85
|
+
// Cleanup timeout on unmount
|
|
86
|
+
(0, react_1.useEffect)(() => {
|
|
87
|
+
return () => {
|
|
88
|
+
if (rectangleClearTimeoutRef.current) {
|
|
89
|
+
clearTimeout(rectangleClearTimeoutRef.current);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}, []);
|
|
84
93
|
const normalizedQuality = (0, react_1.useMemo)(() => {
|
|
85
94
|
if (react_native_1.Platform.OS === 'ios') {
|
|
86
95
|
// iOS expects 0-1
|
|
@@ -281,7 +290,28 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
|
|
|
281
290
|
lastRectangleRef.current = payload.rectangleCoordinates;
|
|
282
291
|
}
|
|
283
292
|
const isGoodRectangle = payload.lastDetectionType === 0;
|
|
284
|
-
|
|
293
|
+
const hasValidRectangle = isGoodRectangle && rectangleOnScreen;
|
|
294
|
+
// 그리드를 표시할 조건: 좋은 품질의 사각형이 화면에 있을 때만
|
|
295
|
+
if (hasValidRectangle) {
|
|
296
|
+
// 기존 타임아웃 클리어
|
|
297
|
+
if (rectangleClearTimeoutRef.current) {
|
|
298
|
+
clearTimeout(rectangleClearTimeoutRef.current);
|
|
299
|
+
}
|
|
300
|
+
setDetectedRectangle(payload);
|
|
301
|
+
// 500ms 후에 그리드 자동 클리어 (새로운 이벤트가 없으면)
|
|
302
|
+
rectangleClearTimeoutRef.current = setTimeout(() => {
|
|
303
|
+
setDetectedRectangle(null);
|
|
304
|
+
rectangleClearTimeoutRef.current = null;
|
|
305
|
+
}, 500);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
// 즉시 클리어
|
|
309
|
+
if (rectangleClearTimeoutRef.current) {
|
|
310
|
+
clearTimeout(rectangleClearTimeoutRef.current);
|
|
311
|
+
rectangleClearTimeoutRef.current = null;
|
|
312
|
+
}
|
|
313
|
+
setDetectedRectangle(null);
|
|
314
|
+
}
|
|
285
315
|
onRectangleDetect?.(payload);
|
|
286
316
|
}, [autoCapture, minStableFrames, onRectangleDetect]);
|
|
287
317
|
(0, react_1.useImperativeHandle)(ref, () => ({
|
package/dist/FullDocScanner.js
CHANGED
|
@@ -110,7 +110,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
110
110
|
const [rectangleDetected, setRectangleDetected] = (0, react_1.useState)(false);
|
|
111
111
|
const [rectangleHint, setRectangleHint] = (0, react_1.useState)(false);
|
|
112
112
|
const [flashEnabled, setFlashEnabled] = (0, react_1.useState)(false);
|
|
113
|
-
const [
|
|
113
|
+
const [rotationDegrees, setRotationDegrees] = (0, react_1.useState)(0);
|
|
114
114
|
const resolvedGridColor = gridColor ?? overlayColor;
|
|
115
115
|
const docScannerRef = (0, react_1.useRef)(null);
|
|
116
116
|
const captureModeRef = (0, react_1.useRef)(null);
|
|
@@ -329,54 +329,57 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
329
329
|
const handleFlashToggle = (0, react_1.useCallback)(() => {
|
|
330
330
|
setFlashEnabled(prev => !prev);
|
|
331
331
|
}, []);
|
|
332
|
-
const handleRotateImage = (0, react_1.useCallback)(
|
|
333
|
-
if (
|
|
332
|
+
const handleRotateImage = (0, react_1.useCallback)((degrees) => {
|
|
333
|
+
if (!croppedImageData)
|
|
334
334
|
return;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
335
|
+
setRotationDegrees(prev => {
|
|
336
|
+
const newRotation = (prev + degrees + 360) % 360;
|
|
337
|
+
return newRotation;
|
|
338
|
+
});
|
|
339
|
+
}, [croppedImageData]);
|
|
340
|
+
const handleConfirm = (0, react_1.useCallback)(async () => {
|
|
341
|
+
if (!croppedImageData)
|
|
342
|
+
return;
|
|
343
|
+
// 회전이 필요한 경우 실제로 이미지를 회전
|
|
344
|
+
if (rotationDegrees !== 0) {
|
|
345
|
+
try {
|
|
346
|
+
const rotatedImage = await react_native_image_crop_picker_1.default.openCropper({
|
|
347
|
+
path: croppedImageData.path,
|
|
348
|
+
mediaType: 'photo',
|
|
349
|
+
cropping: true,
|
|
350
|
+
freeStyleCropEnabled: true,
|
|
351
|
+
includeBase64: true,
|
|
352
|
+
compressImageQuality: 0.9,
|
|
353
|
+
cropperToolbarTitle: ' ',
|
|
354
|
+
cropperChooseText: '완료',
|
|
355
|
+
cropperCancelText: '취소',
|
|
356
|
+
cropperRotateButtonsHidden: false,
|
|
357
|
+
});
|
|
358
|
+
onResult({
|
|
359
|
+
path: rotatedImage.path,
|
|
360
|
+
base64: rotatedImage.data ?? undefined,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
console.error('[FullDocScanner] Image rotation error:', error);
|
|
365
|
+
// 에러 발생 시 원본 이미지 전송
|
|
366
|
+
onResult({
|
|
367
|
+
path: croppedImageData.path,
|
|
368
|
+
base64: croppedImageData.base64,
|
|
369
|
+
});
|
|
363
370
|
}
|
|
364
371
|
}
|
|
365
|
-
|
|
366
|
-
setIsRotating(false);
|
|
367
|
-
}
|
|
368
|
-
}, [isRotating, croppedImageData, emitError]);
|
|
369
|
-
const handleConfirm = (0, react_1.useCallback)(() => {
|
|
370
|
-
if (croppedImageData) {
|
|
372
|
+
else {
|
|
371
373
|
onResult({
|
|
372
374
|
path: croppedImageData.path,
|
|
373
375
|
base64: croppedImageData.base64,
|
|
374
376
|
});
|
|
375
377
|
}
|
|
376
|
-
}, [croppedImageData, onResult]);
|
|
378
|
+
}, [croppedImageData, rotationDegrees, onResult]);
|
|
377
379
|
const handleRetake = (0, react_1.useCallback)(() => {
|
|
378
380
|
console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
|
|
379
381
|
setCroppedImageData(null);
|
|
382
|
+
setRotationDegrees(0);
|
|
380
383
|
setProcessing(false);
|
|
381
384
|
setRectangleDetected(false);
|
|
382
385
|
setRectangleHint(false);
|
|
@@ -447,14 +450,17 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
447
450
|
croppedImageData ? (
|
|
448
451
|
// check_DP: Show confirmation screen
|
|
449
452
|
react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
|
|
450
|
-
react_1.default.createElement(react_native_1.View, { style: styles.
|
|
451
|
-
react_1.default.createElement(react_native_1.TouchableOpacity, { style:
|
|
453
|
+
react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
|
|
454
|
+
react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.rotateButtonTop, onPress: () => handleRotateImage(-90), accessibilityLabel: "\uC67C\uCABD\uC73C\uB85C 90\uB3C4 \uD68C\uC804", accessibilityRole: "button" },
|
|
452
455
|
react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BA"),
|
|
453
456
|
react_1.default.createElement(react_native_1.Text, { style: styles.rotateButtonLabel }, "\uC88C\uB85C 90\u00B0")),
|
|
454
|
-
react_1.default.createElement(react_native_1.TouchableOpacity, { style:
|
|
457
|
+
react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.rotateButtonTop, onPress: () => handleRotateImage(90), accessibilityLabel: "\uC624\uB978\uCABD\uC73C\uB85C 90\uB3C4 \uD68C\uC804", accessibilityRole: "button" },
|
|
455
458
|
react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BB"),
|
|
456
459
|
react_1.default.createElement(react_native_1.Text, { style: styles.rotateButtonLabel }, "\uC6B0\uB85C 90\u00B0"))),
|
|
457
|
-
react_1.default.createElement(react_native_1.Image, { source: { uri: croppedImageData.path }, style:
|
|
460
|
+
react_1.default.createElement(react_native_1.Image, { source: { uri: croppedImageData.path }, style: [
|
|
461
|
+
styles.previewImage,
|
|
462
|
+
{ transform: [{ rotate: `${rotationDegrees}deg` }] }
|
|
463
|
+
], resizeMode: "contain" }),
|
|
458
464
|
react_1.default.createElement(react_native_1.View, { style: styles.confirmationButtons },
|
|
459
465
|
react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.retakeButton], onPress: handleRetake, accessibilityLabel: mergedStrings.retake, accessibilityRole: "button" },
|
|
460
466
|
react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
|
|
@@ -613,6 +619,17 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
613
619
|
gap: 12,
|
|
614
620
|
zIndex: 10,
|
|
615
621
|
},
|
|
622
|
+
rotateButtonsCenter: {
|
|
623
|
+
position: 'absolute',
|
|
624
|
+
top: 60,
|
|
625
|
+
left: 0,
|
|
626
|
+
right: 0,
|
|
627
|
+
flexDirection: 'row',
|
|
628
|
+
justifyContent: 'center',
|
|
629
|
+
alignItems: 'center',
|
|
630
|
+
gap: 12,
|
|
631
|
+
zIndex: 10,
|
|
632
|
+
},
|
|
616
633
|
rotateButtonTop: {
|
|
617
634
|
flexDirection: 'row',
|
|
618
635
|
alignItems: 'center',
|
package/package.json
CHANGED
package/src/DocScanner.tsx
CHANGED
|
@@ -142,6 +142,7 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
|
|
|
142
142
|
const [detectedRectangle, setDetectedRectangle] = useState<RectangleDetectEvent | null>(null);
|
|
143
143
|
const lastRectangleRef = useRef<Rectangle | null>(null);
|
|
144
144
|
const captureOriginRef = useRef<'auto' | 'manual'>('auto');
|
|
145
|
+
const rectangleClearTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
145
146
|
|
|
146
147
|
useEffect(() => {
|
|
147
148
|
if (!autoCapture) {
|
|
@@ -149,6 +150,15 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
|
|
|
149
150
|
}
|
|
150
151
|
}, [autoCapture]);
|
|
151
152
|
|
|
153
|
+
// Cleanup timeout on unmount
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
return () => {
|
|
156
|
+
if (rectangleClearTimeoutRef.current) {
|
|
157
|
+
clearTimeout(rectangleClearTimeoutRef.current);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}, []);
|
|
161
|
+
|
|
152
162
|
const normalizedQuality = useMemo(() => {
|
|
153
163
|
if (Platform.OS === 'ios') {
|
|
154
164
|
// iOS expects 0-1
|
|
@@ -385,7 +395,29 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
|
|
|
385
395
|
}
|
|
386
396
|
|
|
387
397
|
const isGoodRectangle = payload.lastDetectionType === 0;
|
|
388
|
-
|
|
398
|
+
const hasValidRectangle = isGoodRectangle && rectangleOnScreen;
|
|
399
|
+
|
|
400
|
+
// 그리드를 표시할 조건: 좋은 품질의 사각형이 화면에 있을 때만
|
|
401
|
+
if (hasValidRectangle) {
|
|
402
|
+
// 기존 타임아웃 클리어
|
|
403
|
+
if (rectangleClearTimeoutRef.current) {
|
|
404
|
+
clearTimeout(rectangleClearTimeoutRef.current);
|
|
405
|
+
}
|
|
406
|
+
setDetectedRectangle(payload);
|
|
407
|
+
// 500ms 후에 그리드 자동 클리어 (새로운 이벤트가 없으면)
|
|
408
|
+
rectangleClearTimeoutRef.current = setTimeout(() => {
|
|
409
|
+
setDetectedRectangle(null);
|
|
410
|
+
rectangleClearTimeoutRef.current = null;
|
|
411
|
+
}, 500);
|
|
412
|
+
} else {
|
|
413
|
+
// 즉시 클리어
|
|
414
|
+
if (rectangleClearTimeoutRef.current) {
|
|
415
|
+
clearTimeout(rectangleClearTimeoutRef.current);
|
|
416
|
+
rectangleClearTimeoutRef.current = null;
|
|
417
|
+
}
|
|
418
|
+
setDetectedRectangle(null);
|
|
419
|
+
}
|
|
420
|
+
|
|
389
421
|
onRectangleDetect?.(payload);
|
|
390
422
|
},
|
|
391
423
|
[autoCapture, minStableFrames, onRectangleDetect],
|
package/src/FullDocScanner.tsx
CHANGED
|
@@ -152,7 +152,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
152
152
|
const [rectangleDetected, setRectangleDetected] = useState(false);
|
|
153
153
|
const [rectangleHint, setRectangleHint] = useState(false);
|
|
154
154
|
const [flashEnabled, setFlashEnabled] = useState(false);
|
|
155
|
-
const [
|
|
155
|
+
const [rotationDegrees, setRotationDegrees] = useState(0);
|
|
156
156
|
const resolvedGridColor = gridColor ?? overlayColor;
|
|
157
157
|
const docScannerRef = useRef<DocScannerHandle | null>(null);
|
|
158
158
|
const captureModeRef = useRef<'grid' | 'no-grid' | null>(null);
|
|
@@ -440,59 +440,58 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
440
440
|
setFlashEnabled(prev => !prev);
|
|
441
441
|
}, []);
|
|
442
442
|
|
|
443
|
-
const handleRotateImage = useCallback(
|
|
444
|
-
if (
|
|
443
|
+
const handleRotateImage = useCallback((degrees: -90 | 90) => {
|
|
444
|
+
if (!croppedImageData) return;
|
|
445
445
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
446
|
+
setRotationDegrees(prev => {
|
|
447
|
+
const newRotation = (prev + degrees + 360) % 360;
|
|
448
|
+
return newRotation;
|
|
449
|
+
});
|
|
450
|
+
}, [croppedImageData]);
|
|
449
451
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
mediaType: 'photo',
|
|
453
|
-
cropping: true,
|
|
454
|
-
freeStyleCropEnabled: true,
|
|
455
|
-
includeBase64: true,
|
|
456
|
-
compressImageQuality: 0.9,
|
|
457
|
-
cropperToolbarTitle: degrees === 90 ? '↻' : '↺',
|
|
458
|
-
cropperChooseText: '완료',
|
|
459
|
-
cropperCancelText: '취소',
|
|
460
|
-
cropperRotateButtonsHidden: false,
|
|
461
|
-
});
|
|
452
|
+
const handleConfirm = useCallback(async () => {
|
|
453
|
+
if (!croppedImageData) return;
|
|
462
454
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
} finally {
|
|
480
|
-
setIsRotating(false);
|
|
481
|
-
}
|
|
482
|
-
}, [isRotating, croppedImageData, emitError]);
|
|
455
|
+
// 회전이 필요한 경우 실제로 이미지를 회전
|
|
456
|
+
if (rotationDegrees !== 0) {
|
|
457
|
+
try {
|
|
458
|
+
const rotatedImage = await ImageCropPicker.openCropper({
|
|
459
|
+
path: croppedImageData.path,
|
|
460
|
+
mediaType: 'photo',
|
|
461
|
+
cropping: true,
|
|
462
|
+
freeStyleCropEnabled: true,
|
|
463
|
+
includeBase64: true,
|
|
464
|
+
compressImageQuality: 0.9,
|
|
465
|
+
cropperToolbarTitle: ' ',
|
|
466
|
+
cropperChooseText: '완료',
|
|
467
|
+
cropperCancelText: '취소',
|
|
468
|
+
cropperRotateButtonsHidden: false,
|
|
469
|
+
});
|
|
483
470
|
|
|
484
|
-
|
|
485
|
-
|
|
471
|
+
onResult({
|
|
472
|
+
path: rotatedImage.path,
|
|
473
|
+
base64: rotatedImage.data ?? undefined,
|
|
474
|
+
});
|
|
475
|
+
} catch (error) {
|
|
476
|
+
console.error('[FullDocScanner] Image rotation error:', error);
|
|
477
|
+
// 에러 발생 시 원본 이미지 전송
|
|
478
|
+
onResult({
|
|
479
|
+
path: croppedImageData.path,
|
|
480
|
+
base64: croppedImageData.base64,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
486
484
|
onResult({
|
|
487
485
|
path: croppedImageData.path,
|
|
488
486
|
base64: croppedImageData.base64,
|
|
489
487
|
});
|
|
490
488
|
}
|
|
491
|
-
}, [croppedImageData, onResult]);
|
|
489
|
+
}, [croppedImageData, rotationDegrees, onResult]);
|
|
492
490
|
|
|
493
491
|
const handleRetake = useCallback(() => {
|
|
494
492
|
console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
|
|
495
493
|
setCroppedImageData(null);
|
|
494
|
+
setRotationDegrees(0);
|
|
496
495
|
setProcessing(false);
|
|
497
496
|
setRectangleDetected(false);
|
|
498
497
|
setRectangleHint(false);
|
|
@@ -574,12 +573,11 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
574
573
|
{croppedImageData ? (
|
|
575
574
|
// check_DP: Show confirmation screen
|
|
576
575
|
<View style={styles.confirmationContainer}>
|
|
577
|
-
{/*
|
|
578
|
-
<View style={styles.
|
|
576
|
+
{/* 회전 버튼들 - 가운데 정렬 */}
|
|
577
|
+
<View style={styles.rotateButtonsCenter}>
|
|
579
578
|
<TouchableOpacity
|
|
580
|
-
style={
|
|
579
|
+
style={styles.rotateButtonTop}
|
|
581
580
|
onPress={() => handleRotateImage(-90)}
|
|
582
|
-
disabled={isRotating}
|
|
583
581
|
accessibilityLabel="왼쪽으로 90도 회전"
|
|
584
582
|
accessibilityRole="button"
|
|
585
583
|
>
|
|
@@ -587,9 +585,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
587
585
|
<Text style={styles.rotateButtonLabel}>좌로 90°</Text>
|
|
588
586
|
</TouchableOpacity>
|
|
589
587
|
<TouchableOpacity
|
|
590
|
-
style={
|
|
588
|
+
style={styles.rotateButtonTop}
|
|
591
589
|
onPress={() => handleRotateImage(90)}
|
|
592
|
-
disabled={isRotating}
|
|
593
590
|
accessibilityLabel="오른쪽으로 90도 회전"
|
|
594
591
|
accessibilityRole="button"
|
|
595
592
|
>
|
|
@@ -599,7 +596,10 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
599
596
|
</View>
|
|
600
597
|
<Image
|
|
601
598
|
source={{ uri: croppedImageData.path }}
|
|
602
|
-
style={
|
|
599
|
+
style={[
|
|
600
|
+
styles.previewImage,
|
|
601
|
+
{ transform: [{ rotate: `${rotationDegrees}deg` }] }
|
|
602
|
+
]}
|
|
603
603
|
resizeMode="contain"
|
|
604
604
|
/>
|
|
605
605
|
<View style={styles.confirmationButtons}>
|
|
@@ -842,6 +842,17 @@ const styles = StyleSheet.create({
|
|
|
842
842
|
gap: 12,
|
|
843
843
|
zIndex: 10,
|
|
844
844
|
},
|
|
845
|
+
rotateButtonsCenter: {
|
|
846
|
+
position: 'absolute',
|
|
847
|
+
top: 60,
|
|
848
|
+
left: 0,
|
|
849
|
+
right: 0,
|
|
850
|
+
flexDirection: 'row',
|
|
851
|
+
justifyContent: 'center',
|
|
852
|
+
alignItems: 'center',
|
|
853
|
+
gap: 12,
|
|
854
|
+
zIndex: 10,
|
|
855
|
+
},
|
|
845
856
|
rotateButtonTop: {
|
|
846
857
|
flexDirection: 'row',
|
|
847
858
|
alignItems: 'center',
|