@momo-kits/foundation 0.168.1-beta.2 → 0.168.1-beta.20
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/Application/BottomSheet.tsx +142 -37
- package/Application/BottomTab/DuoTabBar.tsx +191 -151
- package/Application/BottomTab/useLiquidSelection.ts +374 -0
- package/Application/ModalScreen.tsx +12 -8
- package/Application/device.ts +24 -1
- package/Input/InputMoney.tsx +130 -178
- package/Input/styles.ts +9 -0
- package/Layout/utils.ts +1 -0
- package/Popup/PopupNotify.tsx +20 -10
- package/Popup/utils.ts +9 -15
- package/package.json +1 -1
package/Input/InputMoney.tsx
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
1
2
|
import React, {
|
|
2
3
|
forwardRef,
|
|
3
4
|
useContext,
|
|
4
5
|
useEffect,
|
|
5
6
|
useImperativeHandle,
|
|
6
|
-
useLayoutEffect,
|
|
7
7
|
useRef,
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react';
|
|
10
10
|
import {
|
|
11
|
+
Platform,
|
|
12
|
+
Pressable,
|
|
13
|
+
Text,
|
|
11
14
|
TextInput,
|
|
12
15
|
TextInputFocusEvent,
|
|
13
|
-
TextInputSelectionChangeEvent,
|
|
14
16
|
TouchableOpacity,
|
|
15
17
|
View,
|
|
16
18
|
} from 'react-native';
|
|
@@ -22,7 +24,6 @@ import {
|
|
|
22
24
|
} from '../Context';
|
|
23
25
|
import { Icon } from '../Icon';
|
|
24
26
|
import { useScaleSize, exportFontFamily } from '../Text';
|
|
25
|
-
import { Spacing } from '../Consts';
|
|
26
27
|
import {
|
|
27
28
|
ErrorView,
|
|
28
29
|
FloatingView,
|
|
@@ -34,93 +35,22 @@ import { InputMoneyProps } from './index';
|
|
|
34
35
|
import styles from './styles';
|
|
35
36
|
import SystemTextInput from './SystemTextInput';
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
type Edit = { start: number; end: number; inserted: string };
|
|
38
|
+
const moneyFormatter = new Intl.NumberFormat('vi-VN');
|
|
39
39
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const caretFromDigitIndex = (display: string, digitIndex: number) => {
|
|
46
|
-
let count = 0;
|
|
47
|
-
for (let i = 0; i < display.length && count < digitIndex; i++) {
|
|
48
|
-
if (/\d/.test(display[i])) {
|
|
49
|
-
count += 1;
|
|
50
|
-
if (count === digitIndex) return i + 1;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return 0;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const detectEdit = (old: string, next: string, selection: Selection): Edit => {
|
|
57
|
-
const { start, end } = selection;
|
|
58
|
-
const removed = old.length - next.length;
|
|
59
|
-
const candidates: Edit[] = [
|
|
60
|
-
{
|
|
61
|
-
start,
|
|
62
|
-
end,
|
|
63
|
-
inserted: next.slice(start, next.length - (old.length - end)),
|
|
64
|
-
},
|
|
65
|
-
{ start: start - removed, end: start, inserted: '' },
|
|
66
|
-
{ start, end: start + removed, inserted: '' },
|
|
67
|
-
];
|
|
68
|
-
const match = candidates.find(
|
|
69
|
-
e =>
|
|
70
|
-
e.start >= 0 &&
|
|
71
|
-
e.end >= e.start &&
|
|
72
|
-
e.end <= old.length &&
|
|
73
|
-
old.slice(0, e.start) + e.inserted + old.slice(e.end) === next,
|
|
74
|
-
);
|
|
75
|
-
if (match) return match;
|
|
76
|
-
|
|
77
|
-
let prefix = 0;
|
|
78
|
-
while (
|
|
79
|
-
prefix < Math.min(old.length, next.length) &&
|
|
80
|
-
old[prefix] === next[prefix]
|
|
81
|
-
) {
|
|
82
|
-
prefix += 1;
|
|
40
|
+
const findEdit = (old: string, text: string) => {
|
|
41
|
+
let start = 0;
|
|
42
|
+
while (start < text.length && old[start] === text[start]) {
|
|
43
|
+
start += 1;
|
|
83
44
|
}
|
|
84
45
|
let suffix = 0;
|
|
85
46
|
while (
|
|
86
|
-
suffix < Math.min(
|
|
87
|
-
old[old.length - 1 - suffix] ===
|
|
47
|
+
suffix < Math.min(text.length, old.length) - start &&
|
|
48
|
+
old[old.length - 1 - suffix] === text[text.length - 1 - suffix]
|
|
88
49
|
) {
|
|
89
50
|
suffix += 1;
|
|
90
51
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
end: old.length - suffix,
|
|
94
|
-
inserted: next.slice(prefix, next.length - suffix),
|
|
95
|
-
};
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const applyEdit = (display: string, edit: Edit, digitLimit: number) => {
|
|
99
|
-
const digits = display.replace(/\D/g, '');
|
|
100
|
-
let left = countDigits(display.slice(0, edit.start));
|
|
101
|
-
let removed = countDigits(display.slice(edit.start, edit.end));
|
|
102
|
-
const inserted = edit.inserted.replace(/\D/g, '');
|
|
103
|
-
if (
|
|
104
|
-
!removed &&
|
|
105
|
-
!inserted &&
|
|
106
|
-
left > 0 &&
|
|
107
|
-
display.slice(edit.start, edit.end).includes('.')
|
|
108
|
-
) {
|
|
109
|
-
left -= 1;
|
|
110
|
-
removed = 1;
|
|
111
|
-
}
|
|
112
|
-
const next = digits.slice(0, left) + inserted + digits.slice(left + removed);
|
|
113
|
-
const trimmed = next.replace(/^0+(?=\d)/, '');
|
|
114
|
-
if ((!removed && !inserted) || trimmed.length > digitLimit) {
|
|
115
|
-
return { digits, caretDigit: left };
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
digits: trimmed,
|
|
119
|
-
caretDigit: Math.max(
|
|
120
|
-
0,
|
|
121
|
-
left + inserted.length - (next.length - trimmed.length),
|
|
122
|
-
),
|
|
123
|
-
};
|
|
52
|
+
const size = old.length + text.length - 2 * (start + suffix);
|
|
53
|
+
return { old, start, suffix, size };
|
|
124
54
|
};
|
|
125
55
|
|
|
126
56
|
const InputMoney = forwardRef(
|
|
@@ -160,14 +90,15 @@ const InputMoney = forwardRef(
|
|
|
160
90
|
const inputFontFamily = exportFontFamily('bold');
|
|
161
91
|
const [focused, setFocused] = useState(false);
|
|
162
92
|
const [displayValue, setDisplayValue] = useState('');
|
|
163
|
-
const [
|
|
164
|
-
const
|
|
165
|
-
const digitsRef = useRef('');
|
|
166
|
-
const selectionRef = useRef<Selection>({ start: 0, end: 0 });
|
|
167
|
-
const pendingCaretRef = useRef<number | null>(null);
|
|
168
|
-
const pendingSelectionRef = useRef<Selection | null>(null);
|
|
169
|
-
const editingRef = useRef(false);
|
|
93
|
+
const [numericValue, setNumericValue] = useState('');
|
|
94
|
+
const [lastDisplayValue, setLastDisplayValue] = useState('');
|
|
170
95
|
const inputRef = useRef<TextInput>(null);
|
|
96
|
+
const caretDigitsAfter = useRef<number | undefined>(undefined);
|
|
97
|
+
const lastRawText = useRef('');
|
|
98
|
+
const [caretSelection, setCaretSelection] = useState<{
|
|
99
|
+
start: number;
|
|
100
|
+
end: number;
|
|
101
|
+
}>();
|
|
171
102
|
const componentName = 'InputMoney';
|
|
172
103
|
|
|
173
104
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
@@ -177,94 +108,103 @@ const InputMoney = forwardRef(
|
|
|
177
108
|
props.accessibilityLabel,
|
|
178
109
|
);
|
|
179
110
|
|
|
180
|
-
const
|
|
111
|
+
const maxLength = (props as any)?.maxLength;
|
|
112
|
+
const hasMaxLength = typeof maxLength === 'number';
|
|
113
|
+
const digitLimit = hasMaxLength ? maxLength : Number.POSITIVE_INFINITY;
|
|
181
114
|
|
|
182
115
|
useEffect(() => {
|
|
183
116
|
if (value !== undefined && typeof value === 'string') {
|
|
184
117
|
const cleanValue = value.replace(/\D/g, '').replace(/^0+(?=\d)/, '');
|
|
185
|
-
if (cleanValue !==
|
|
186
|
-
|
|
118
|
+
if (cleanValue !== numericValue) {
|
|
119
|
+
caretDigitsAfter.current = undefined;
|
|
120
|
+
setNumericValue(cleanValue);
|
|
121
|
+
updateDisplayValue(cleanValue);
|
|
187
122
|
}
|
|
188
123
|
}
|
|
189
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
190
124
|
}, [value]);
|
|
191
125
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
pendingCaretRef.current = null;
|
|
196
|
-
inputRef.current.setSelection(caret, caret);
|
|
197
|
-
}, [commitCount]);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
setLastDisplayValue(displayValue);
|
|
128
|
+
}, [displayValue]);
|
|
198
129
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
setDisplayValue(
|
|
209
|
-
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!caretSelection) return;
|
|
132
|
+
setCaretSelection(current =>
|
|
133
|
+
current === caretSelection ? undefined : current,
|
|
134
|
+
);
|
|
135
|
+
}, [caretSelection]);
|
|
136
|
+
|
|
137
|
+
const updateDisplayValue = (numeric: string) => {
|
|
138
|
+
const next = numeric ? moneyFormatter.format(Number(numeric)) : '';
|
|
139
|
+
setDisplayValue(next);
|
|
140
|
+
|
|
141
|
+
const digitsAfter = caretDigitsAfter.current;
|
|
142
|
+
caretDigitsAfter.current = undefined;
|
|
143
|
+
if (Platform.OS !== 'android' || digitsAfter === undefined) return;
|
|
144
|
+
let before = parseInputText(next).length - digitsAfter;
|
|
145
|
+
let caret = 0;
|
|
146
|
+
while (before > 0 && caret < next.length) {
|
|
147
|
+
if (/\d/.test(next[caret])) before -= 1;
|
|
148
|
+
caret += 1;
|
|
149
|
+
}
|
|
150
|
+
setCaretSelection({ start: caret, end: caret });
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const parseInputText = (text: string): string => text.replace(/\D/g, '');
|
|
154
|
+
|
|
155
|
+
const updateNumericValue = (val: string) => {
|
|
156
|
+
let next = val.replace(/[^\d]/g, '').replace(/^0+(?=\d)/, '');
|
|
157
|
+
if (next.length > digitLimit) next = next.slice(0, digitLimit);
|
|
158
|
+
if (/^\d*$/.test(next)) {
|
|
159
|
+
setNumericValue(next);
|
|
160
|
+
updateDisplayValue(next);
|
|
161
|
+
onChangeText?.(next);
|
|
162
|
+
}
|
|
210
163
|
};
|
|
211
164
|
|
|
212
165
|
const onClearText = () => {
|
|
213
|
-
|
|
166
|
+
inputRef?.current?.clear();
|
|
167
|
+
inputRef.current?.setNativeProps({ text: '' });
|
|
168
|
+
setDisplayValue('');
|
|
169
|
+
setNumericValue('');
|
|
214
170
|
onChangeText?.('');
|
|
215
171
|
};
|
|
216
172
|
|
|
217
173
|
const _onChangeText = (text: string) => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
});
|
|
225
|
-
if (text) {
|
|
226
|
-
const edit = detectEdit(displayRef.current, text, selectionRef.current);
|
|
227
|
-
({ digits, caretDigit } = applyEdit(
|
|
228
|
-
displayRef.current,
|
|
229
|
-
edit,
|
|
230
|
-
digitLimit,
|
|
231
|
-
));
|
|
174
|
+
if (!text) {
|
|
175
|
+
lastRawText.current = '';
|
|
176
|
+
setDisplayValue('');
|
|
177
|
+
setNumericValue('');
|
|
178
|
+
onChangeText?.('');
|
|
179
|
+
return;
|
|
232
180
|
}
|
|
233
|
-
|
|
234
|
-
|
|
181
|
+
const digits = parseInputText(text);
|
|
182
|
+
const raw = lastRawText.current;
|
|
183
|
+
lastRawText.current = text;
|
|
184
|
+
const formattedEdit = findEdit(lastDisplayValue, text);
|
|
185
|
+
const rawEdit = findEdit(raw, text);
|
|
186
|
+
const { old, start, suffix } =
|
|
187
|
+
raw && rawEdit.size < formattedEdit.size ? rawEdit : formattedEdit;
|
|
188
|
+
caretDigitsAfter.current = parseInputText(
|
|
189
|
+
text.slice(text.length - suffix),
|
|
190
|
+
).length;
|
|
191
|
+
if (text.length < old.length && digits === numericValue) {
|
|
192
|
+
const index = parseInputText(text.slice(0, start)).length - 1;
|
|
193
|
+
if (index >= 0) {
|
|
194
|
+
updateNumericValue(digits.slice(0, index) + digits.slice(index + 1));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
235
197
|
}
|
|
198
|
+
updateNumericValue(digits);
|
|
236
199
|
};
|
|
237
200
|
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const selection = e.nativeEvent.selection;
|
|
242
|
-
pendingSelectionRef.current = selection;
|
|
243
|
-
setTimeout(() => {
|
|
244
|
-
if (pendingSelectionRef.current !== selection) return;
|
|
245
|
-
pendingSelectionRef.current = null;
|
|
246
|
-
const { start, end } = selection;
|
|
247
|
-
const display = displayRef.current;
|
|
248
|
-
const max = display ? display.length - currency.length : 0;
|
|
249
|
-
const clamped = {
|
|
250
|
-
start: Math.min(start, max),
|
|
251
|
-
end: Math.min(end, max),
|
|
252
|
-
};
|
|
253
|
-
selectionRef.current = clamped;
|
|
254
|
-
if (clamped.start !== start || clamped.end !== end) {
|
|
255
|
-
inputRef.current?.setSelection(clamped.start, clamped.end);
|
|
256
|
-
}
|
|
257
|
-
});
|
|
201
|
+
const focusAtEnd = () => {
|
|
202
|
+
inputRef.current?.focus();
|
|
203
|
+
inputRef.current?.setSelection(displayValue.length, displayValue.length);
|
|
258
204
|
};
|
|
259
205
|
|
|
260
206
|
const _onFocus = (e: TextInputFocusEvent) => {
|
|
261
207
|
setFocused(true);
|
|
262
|
-
const caret = pendingCaretRef.current;
|
|
263
|
-
pendingCaretRef.current = null;
|
|
264
|
-
if (caret !== null && !props.selectTextOnFocus) {
|
|
265
|
-
selectionRef.current = { start: caret, end: caret };
|
|
266
|
-
inputRef.current?.setSelection(caret, caret);
|
|
267
|
-
}
|
|
268
208
|
onFocus?.(e);
|
|
269
209
|
};
|
|
270
210
|
|
|
@@ -274,13 +214,15 @@ const InputMoney = forwardRef(
|
|
|
274
214
|
};
|
|
275
215
|
|
|
276
216
|
const _setText = (text: string) => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
217
|
+
caretDigitsAfter.current = undefined;
|
|
218
|
+
if (!text) {
|
|
219
|
+
setDisplayValue('');
|
|
220
|
+
setNumericValue('');
|
|
221
|
+
onChangeText?.('');
|
|
222
|
+
return;
|
|
283
223
|
}
|
|
224
|
+
const newNumericValue = parseInputText(text);
|
|
225
|
+
updateNumericValue(newNumericValue);
|
|
284
226
|
};
|
|
285
227
|
|
|
286
228
|
useImperativeHandle(ref, () => {
|
|
@@ -302,6 +244,14 @@ const InputMoney = forwardRef(
|
|
|
302
244
|
placeholderColor = disabledColor;
|
|
303
245
|
iconTintColor = disabledColor;
|
|
304
246
|
}
|
|
247
|
+
const textStyle = {
|
|
248
|
+
fontSize: scaledFontSize,
|
|
249
|
+
fontFamily: inputFontFamily,
|
|
250
|
+
};
|
|
251
|
+
const placeholderHasCurrency = placeholder.endsWith(currency);
|
|
252
|
+
const inputPlaceholder = placeholderHasCurrency
|
|
253
|
+
? placeholder.slice(0, -currency.length)
|
|
254
|
+
: placeholder;
|
|
305
255
|
return (
|
|
306
256
|
<View
|
|
307
257
|
style={[
|
|
@@ -320,44 +270,46 @@ const InputMoney = forwardRef(
|
|
|
320
270
|
onPress={onPressFloatingIcon}
|
|
321
271
|
componentId={componentId}
|
|
322
272
|
/>
|
|
323
|
-
<
|
|
273
|
+
<Pressable
|
|
274
|
+
style={[styles.inputView, styles.moneyInputView]}
|
|
275
|
+
onPress={focusAtEnd}
|
|
276
|
+
disabled={disabled}
|
|
277
|
+
>
|
|
324
278
|
<SystemTextInput
|
|
325
279
|
{...props}
|
|
326
280
|
accessibilityLabel={`${componentId}`}
|
|
327
281
|
textAlignVertical="center"
|
|
328
282
|
ref={inputRef}
|
|
329
|
-
editable={props.editable !== false && !disabled}
|
|
330
283
|
accessibilityState={{
|
|
331
284
|
disabled: disabled,
|
|
332
285
|
...props.accessibilityState,
|
|
333
286
|
}}
|
|
334
|
-
style={[
|
|
335
|
-
{
|
|
336
|
-
width: '100%',
|
|
337
|
-
paddingLeft: Spacing.M,
|
|
338
|
-
height: '100%',
|
|
339
|
-
fontSize: scaledFontSize,
|
|
340
|
-
paddingVertical: Spacing.S,
|
|
341
|
-
fontFamily: inputFontFamily,
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
color: textColor,
|
|
345
|
-
},
|
|
346
|
-
]}
|
|
287
|
+
style={[textStyle, styles.moneyTextInput, { color: textColor }]}
|
|
347
288
|
allowFontScaling={false}
|
|
348
289
|
textBreakStrategy="highQuality"
|
|
349
290
|
value={displayValue}
|
|
291
|
+
selection={caretSelection ?? props.selection}
|
|
350
292
|
onChangeText={_onChangeText}
|
|
351
|
-
onSelectionChange={_onSelectionChange}
|
|
352
293
|
onFocus={_onFocus}
|
|
353
294
|
onBlur={_onBlur}
|
|
354
|
-
placeholder={
|
|
295
|
+
placeholder={inputPlaceholder}
|
|
355
296
|
selectionColor={theme.colors.primary}
|
|
356
297
|
placeholderTextColor={placeholderColor}
|
|
357
298
|
keyboardType={'numeric'}
|
|
358
299
|
maxLength={undefined}
|
|
359
300
|
/>
|
|
360
|
-
|
|
301
|
+
{displayValue || placeholderHasCurrency ? (
|
|
302
|
+
<Text
|
|
303
|
+
style={[
|
|
304
|
+
textStyle,
|
|
305
|
+
{ color: displayValue ? textColor : placeholderColor },
|
|
306
|
+
]}
|
|
307
|
+
allowFontScaling={false}
|
|
308
|
+
>
|
|
309
|
+
{currency}
|
|
310
|
+
</Text>
|
|
311
|
+
) : null}
|
|
312
|
+
</Pressable>
|
|
361
313
|
<View style={styles.iconView}>
|
|
362
314
|
{showClearIcon && focused && displayValue ? (
|
|
363
315
|
<TouchableOpacity
|
package/Input/styles.ts
CHANGED
|
@@ -218,4 +218,13 @@ export default StyleSheet.create({
|
|
|
218
218
|
marginLeft: Spacing.M,
|
|
219
219
|
},
|
|
220
220
|
debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
|
|
221
|
+
moneyInputView: {
|
|
222
|
+
paddingLeft: Spacing.M,
|
|
223
|
+
},
|
|
224
|
+
moneyTextInput: {
|
|
225
|
+
flexShrink: 1,
|
|
226
|
+
paddingLeft: 0,
|
|
227
|
+
paddingRight: Spacing.XXS,
|
|
228
|
+
paddingVertical: 0,
|
|
229
|
+
},
|
|
221
230
|
});
|
package/Layout/utils.ts
CHANGED
package/Popup/PopupNotify.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { Image } from '../Image';
|
|
|
8
8
|
import { Text, useScaleSize } from '../Text';
|
|
9
9
|
import { PopupNotifyProps } from './types';
|
|
10
10
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
11
|
-
import { usePopupMaxHeight, usePopupWidth } from './utils';
|
|
11
|
+
import { getPopupRadius, usePopupMaxHeight, usePopupWidth } from './utils';
|
|
12
12
|
|
|
13
13
|
const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
14
14
|
image,
|
|
@@ -26,6 +26,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
26
26
|
const [scrollContent, setScrollContent] = useState(false);
|
|
27
27
|
const popupWidth = usePopupWidth();
|
|
28
28
|
const maxHeight = usePopupMaxHeight();
|
|
29
|
+
const radius = getPopupRadius();
|
|
29
30
|
const scaledMaxHeight = useScaleSize(172);
|
|
30
31
|
|
|
31
32
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
@@ -170,7 +171,15 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
170
171
|
|
|
171
172
|
const body = (
|
|
172
173
|
<>
|
|
173
|
-
{!!image &&
|
|
174
|
+
{!!image && (
|
|
175
|
+
<Image
|
|
176
|
+
source={{ uri: image }}
|
|
177
|
+
style={[
|
|
178
|
+
styles.image,
|
|
179
|
+
{ borderTopLeftRadius: radius, borderTopRightRadius: radius },
|
|
180
|
+
]}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
174
183
|
<View style={styles.content}>
|
|
175
184
|
<Text
|
|
176
185
|
typography={'header_default_bold'}
|
|
@@ -218,9 +227,9 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
218
227
|
<View
|
|
219
228
|
accessibilityLabel={'popup_notify'}
|
|
220
229
|
style={[
|
|
221
|
-
styles.container,
|
|
222
230
|
{
|
|
223
231
|
backgroundColor: theme.colors.background.surface,
|
|
232
|
+
borderRadius: radius,
|
|
224
233
|
width: popupWidth,
|
|
225
234
|
maxHeight,
|
|
226
235
|
},
|
|
@@ -231,7 +240,13 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
231
240
|
body
|
|
232
241
|
) : (
|
|
233
242
|
// scrolls when iPhone Duo landscape is too short; actions stay visible
|
|
234
|
-
<ScrollView
|
|
243
|
+
<ScrollView
|
|
244
|
+
style={[
|
|
245
|
+
styles.body,
|
|
246
|
+
{ borderTopLeftRadius: radius, borderTopRightRadius: radius },
|
|
247
|
+
]}
|
|
248
|
+
bounces={false}
|
|
249
|
+
>
|
|
235
250
|
{body}
|
|
236
251
|
</ScrollView>
|
|
237
252
|
)}
|
|
@@ -242,17 +257,12 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
242
257
|
};
|
|
243
258
|
|
|
244
259
|
const styles = StyleSheet.create({
|
|
245
|
-
container: {
|
|
246
|
-
borderRadius: Radius.L,
|
|
247
|
-
},
|
|
248
260
|
debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
|
|
249
261
|
image: {
|
|
250
|
-
borderTopLeftRadius: Radius.L,
|
|
251
|
-
borderTopRightRadius: Radius.L,
|
|
252
262
|
aspectRatio: 1.7777,
|
|
253
263
|
overflow: 'hidden',
|
|
254
264
|
},
|
|
255
|
-
body: { flexShrink: 1 },
|
|
265
|
+
body: { flexShrink: 1, overflow: 'hidden' },
|
|
256
266
|
content: { padding: Spacing.XL },
|
|
257
267
|
description: {
|
|
258
268
|
marginTop: Spacing.S,
|
package/Popup/utils.ts
CHANGED
|
@@ -1,32 +1,26 @@
|
|
|
1
1
|
import { Dimensions, useWindowDimensions } from 'react-native';
|
|
2
2
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
3
|
-
import { Spacing } from '../Consts';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
// width of a system alert on the iPhone Duo inner display, in both orientations
|
|
7
|
-
const DUO_POPUP_MAX_WIDTH = 440;
|
|
3
|
+
import { Radius, Spacing } from '../Consts';
|
|
4
|
+
import { getDuoModalBounds, isIPhoneDuo } from '../Application/device';
|
|
8
5
|
|
|
9
6
|
const usePopupWidth = () => {
|
|
10
|
-
const
|
|
7
|
+
const insets = useSafeAreaInsets();
|
|
8
|
+
const { width, height } = useWindowDimensions();
|
|
11
9
|
if (!isIPhoneDuo()) {
|
|
12
10
|
return Dimensions.get('window').width - Spacing.XL * 2;
|
|
13
11
|
}
|
|
14
|
-
return
|
|
12
|
+
return getDuoModalBounds(insets, width, height).maxWidth;
|
|
15
13
|
};
|
|
16
14
|
|
|
17
|
-
// room left for a popup on iPhone Duo, where landscape is too short for it
|
|
18
15
|
const usePopupMaxHeight = () => {
|
|
19
16
|
const insets = useSafeAreaInsets();
|
|
20
17
|
const { width, height } = useWindowDimensions();
|
|
21
18
|
if (!isIPhoneDuo()) {
|
|
22
19
|
return undefined;
|
|
23
20
|
}
|
|
24
|
-
return (
|
|
25
|
-
height -
|
|
26
|
-
getDuoTopInset(insets, width, height) -
|
|
27
|
-
insets.bottom -
|
|
28
|
-
Spacing.XL * 2
|
|
29
|
-
);
|
|
21
|
+
return getDuoModalBounds(insets, width, height).maxHeight;
|
|
30
22
|
};
|
|
31
23
|
|
|
32
|
-
|
|
24
|
+
const getPopupRadius = () => (isIPhoneDuo() ? Radius.XL : Radius.L);
|
|
25
|
+
|
|
26
|
+
export { usePopupWidth, usePopupMaxHeight, getPopupRadius };
|