@momo-kits/foundation 0.168.1-beta.32 → 0.168.1-beta.34

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.
@@ -17,7 +17,6 @@ import {
17
17
  Platform,
18
18
  Pressable,
19
19
  StyleSheet,
20
- TouchableOpacity,
21
20
  useWindowDimensions,
22
21
  View,
23
22
  } from 'react-native';
@@ -26,7 +25,6 @@ import { ApplicationContext, MiniAppContext } from '../Context';
26
25
  import { BottomSheetParams } from './types';
27
26
  import { Colors, Radius, Spacing, Styles } from '../Consts';
28
27
  import { Text } from '../Text';
29
- import { Icon } from '../Icon';
30
28
  import { useHeaderHeight } from '@react-navigation/elements';
31
29
  import Animated, {
32
30
  Easing,
@@ -41,13 +39,11 @@ import { scheduleOnRN } from 'react-native-worklets';
41
39
  import layoutStyles from '../Layout/styles';
42
40
  import { isIPhoneDuo } from './device';
43
41
  import { useModalLayout } from './modalLayout';
42
+ import { MODAL_ICON_BUTTON_SIZE, ModalIconButton } from './ModalIconButton';
44
43
 
45
44
  const INDICATOR_HEIGHT = 16;
46
45
  const HEADER_HEIGHT = 56;
47
46
  const DUO_ORIENTATIONS = ['portrait', 'landscape'];
48
- // foldable spec: icon button 32, icon 20
49
- const FOLDABLE_ICON_BUTTON_SIZE = 32;
50
- const FOLDABLE_ICON_SIZE = 20;
51
47
 
52
48
  const BottomSheet: React.FC<BottomSheetParams> = props => {
53
49
  const { theme, navigator } = useContext(ApplicationContext);
@@ -246,11 +242,6 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
246
242
  * render header
247
243
  */
248
244
  const renderHeader = useCallback(() => {
249
- const iconButtonStyle = [
250
- styles.iconButton,
251
- isFoldable && styles.foldableIconButton,
252
- ];
253
- const iconSize = isFoldable ? FOLDABLE_ICON_SIZE : undefined;
254
245
  return (
255
246
  <View {...panResponder.panHandlers}>
256
247
  <View style={{ height: INDICATOR_HEIGHT }}>
@@ -266,16 +257,14 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
266
257
  ]}
267
258
  >
268
259
  {showBack ? (
269
- <TouchableOpacity style={iconButtonStyle} onPress={onPressBack}>
270
- <Icon source={iconBack} size={iconSize} />
271
- </TouchableOpacity>
260
+ <ModalIconButton icon={iconBack} onPress={onPressBack} />
272
261
  ) : null}
273
262
 
274
263
  {options.header ? (
275
264
  <View style={Styles.flex}>{options.header}</View>
276
265
  ) : (
277
266
  <>
278
- {!leftOptions ? <View style={iconButtonStyle} /> : null}
267
+ {!leftOptions ? <View style={styles.iconButtonSpace} /> : null}
279
268
  <Text
280
269
  style={[Styles.flex, styles.title]}
281
270
  numberOfLines={1}
@@ -285,15 +274,12 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
285
274
  </Text>
286
275
  </>
287
276
  )}
288
- <TouchableOpacity style={iconButtonStyle} onPress={onPressClose}>
289
- <Icon source="navigation_close" size={iconSize} />
290
- </TouchableOpacity>
277
+ <ModalIconButton icon="navigation_close" onPress={onPressClose} />
291
278
  </View>
292
279
  </View>
293
280
  );
294
281
  }, [
295
282
  iconBack,
296
- isFoldable,
297
283
  leftOptions,
298
284
  onPressBack,
299
285
  onPressClose,
@@ -445,18 +431,13 @@ const styles = StyleSheet.create({
445
431
  height: HEADER_HEIGHT,
446
432
  flexDirection: 'row',
447
433
  alignItems: 'center',
434
+ paddingHorizontal: Spacing.M,
435
+ gap: Spacing.S,
448
436
  },
449
437
  title: { textAlign: 'center' },
450
- iconButton: {
451
- width: 24,
452
- height: 24,
453
- alignItems: 'center',
454
- justifyContent: 'center',
455
- marginHorizontal: Spacing.M,
456
- },
457
- foldableIconButton: {
458
- width: FOLDABLE_ICON_BUTTON_SIZE,
459
- height: FOLDABLE_ICON_BUTTON_SIZE,
438
+ iconButtonSpace: {
439
+ width: MODAL_ICON_BUTTON_SIZE,
440
+ height: MODAL_ICON_BUTTON_SIZE,
460
441
  },
461
442
  shrink: { flexShrink: 1 },
462
443
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
@@ -0,0 +1,66 @@
1
+ import React, { useContext } from 'react';
2
+ import {
3
+ StyleProp,
4
+ StyleSheet,
5
+ TouchableOpacity,
6
+ ViewStyle,
7
+ } from 'react-native';
8
+ import { ApplicationContext } from '../Context';
9
+ import { Radius, Spacing } from '../Consts';
10
+ import { Icon } from '../Icon';
11
+
12
+ // modal spec: icon button 32, icon 20, shared with the Compose / SwiftUI kits
13
+ const MODAL_ICON_BUTTON_SIZE = Spacing.XXL;
14
+ const MODAL_ICON_SIZE = 20;
15
+
16
+ type ModalIconButtonProps = {
17
+ icon: string;
18
+ onPress: () => void;
19
+ accessibilityLabel?: string;
20
+ style?: StyleProp<ViewStyle>;
21
+ };
22
+
23
+ /**
24
+ * Close / back button of popups and bottom sheet headers
25
+ */
26
+ const ModalIconButton: React.FC<ModalIconButtonProps> = ({
27
+ icon,
28
+ onPress,
29
+ accessibilityLabel,
30
+ style,
31
+ }) => {
32
+ const { theme } = useContext(ApplicationContext);
33
+ return (
34
+ <TouchableOpacity
35
+ accessibilityLabel={accessibilityLabel}
36
+ onPress={onPress}
37
+ style={[
38
+ styles.button,
39
+ {
40
+ backgroundColor: theme.colors.background.surface,
41
+ borderColor: theme.colors.border.default,
42
+ },
43
+ style,
44
+ ]}
45
+ >
46
+ <Icon
47
+ source={icon}
48
+ color={theme.colors.text.default}
49
+ size={MODAL_ICON_SIZE}
50
+ />
51
+ </TouchableOpacity>
52
+ );
53
+ };
54
+
55
+ const styles = StyleSheet.create({
56
+ button: {
57
+ width: MODAL_ICON_BUTTON_SIZE,
58
+ height: MODAL_ICON_BUTTON_SIZE,
59
+ borderRadius: Radius.L,
60
+ borderWidth: 1,
61
+ alignItems: 'center',
62
+ justifyContent: 'center',
63
+ },
64
+ });
65
+
66
+ export { ModalIconButton, MODAL_ICON_BUTTON_SIZE };
@@ -46,7 +46,7 @@ const BadgeRibbon: FC<BadgeRibbonProps> = ({
46
46
  >
47
47
  <View
48
48
  collapsable={false}
49
- style={{ transform: textTransform, marginVertical: -2 }}
49
+ style={[styles.ribbonLabelWrapper, { transform: textTransform }]}
50
50
  >
51
51
  <Text
52
52
  style={styles.ribbonLabel}
@@ -60,7 +60,7 @@ const BadgeRibbon: FC<BadgeRibbonProps> = ({
60
60
  );
61
61
 
62
62
  const renderSkewContent = () => (
63
- <View style={{ flexDirection: 'row', marginTop: skewMargin }}>
63
+ <View style={[styles.ribbonSkewRow, { marginTop: skewMargin }]}>
64
64
  <View
65
65
  collapsable={false}
66
66
  style={[
@@ -70,7 +70,7 @@ const BadgeRibbon: FC<BadgeRibbonProps> = ({
70
70
  >
71
71
  <View
72
72
  collapsable={false}
73
- style={{ transform: textTransform, marginVertical: -2 }}
73
+ style={[styles.ribbonLabelWrapper, { transform: textTransform }]}
74
74
  >
75
75
  <Text
76
76
  style={styles.ribbonLabel}
package/Badge/styles.ts CHANGED
@@ -118,6 +118,8 @@ export default StyleSheet.create({
118
118
  ribbonLabel: {
119
119
  color: Colors.black_01,
120
120
  },
121
+ ribbonLabelWrapper: {marginVertical: -2},
122
+ ribbonSkewRow: {flexDirection: 'row'},
121
123
  skew: {width: 8, height: 16},
122
124
  tail: {width: 5, height: 20},
123
125
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
@@ -1,23 +1,21 @@
1
1
  import React, { useContext, useRef, useState } from 'react';
2
- import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
2
+ import { ScrollView, StyleSheet, View } from 'react-native';
3
3
  import { ApplicationContext, MiniAppContext } from '../Context';
4
4
  import { Button } from '../Button';
5
- import { Colors, Radius, Spacing, Styles } from '../Consts';
6
- import { Icon } from '../Icon';
5
+ import { Colors, Spacing, Styles } from '../Consts';
7
6
  import { Image } from '../Image';
8
7
  import { Text, useScaleSize } from '../Text';
9
8
  import { PopupNotifyProps } from './types';
10
9
  import { scheduleOnRN } from 'react-native-worklets';
11
10
  import { useModalLayout } from '../Application/modalLayout';
12
-
13
- // foldable spec: icon button 32, icon 20
14
- const FOLDABLE_CLOSE_SIZE = 32;
15
- const FOLDABLE_CLOSE_ICON_SIZE = 20;
11
+ import { ModalIconButton } from '../Application/ModalIconButton';
12
+ import { splitHighlightText } from './utils';
16
13
 
17
14
  const PopupNotify: React.FC<PopupNotifyProps> = ({
18
15
  image,
19
16
  title = 'Title',
20
17
  description = 'Description',
18
+ highlightText,
21
19
  error,
22
20
  secondary,
23
21
  primary,
@@ -142,6 +140,26 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
142
140
  return renderColumn();
143
141
  };
144
142
 
143
+ /**
144
+ * build description text, highlighted keywords keep size/lineHeight and use weight 600
145
+ */
146
+ const renderDescription = (text: string) => {
147
+ const segments = splitHighlightText(text, highlightText);
148
+ if (!segments) {
149
+ return text;
150
+ }
151
+ return segments.map((segment, index) => {
152
+ if (segment.highlight) {
153
+ return (
154
+ <Text key={index} typography={'header_s_semibold'}>
155
+ {segment.text}
156
+ </Text>
157
+ );
158
+ }
159
+ return segment.text;
160
+ });
161
+ };
162
+
145
163
  /**
146
164
  * build close action
147
165
  */
@@ -149,49 +167,16 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
149
167
  if (typeof onIconClose !== 'function') {
150
168
  return null;
151
169
  }
152
- if (isFoldable) {
153
- return (
154
- <TouchableOpacity
155
- accessibilityLabel={'ic_popup_close'}
156
- style={styles.foldableClose}
157
- onPress={() => {
158
- closeAction.current = 'icon_close';
159
- onAction(onIconClose);
160
- }}
161
- >
162
- <Icon
163
- source="navigation_close"
164
- color={theme.colors.text.default}
165
- size={FOLDABLE_CLOSE_ICON_SIZE}
166
- />
167
- </TouchableOpacity>
168
- );
169
- }
170
170
  return (
171
- <TouchableOpacity
171
+ <ModalIconButton
172
+ icon="navigation_close"
172
173
  accessibilityLabel={'ic_popup_close'}
173
- style={styles.iconCloseContainer}
174
+ style={styles.iconClose}
174
175
  onPress={() => {
175
176
  closeAction.current = 'icon_close';
176
177
  onAction(onIconClose);
177
178
  }}
178
- >
179
- <View
180
- style={[
181
- styles.iconClose,
182
- {
183
- backgroundColor: theme.colors.text.default,
184
- borderColor: theme.colors.background.surface,
185
- },
186
- ]}
187
- >
188
- <Icon
189
- source="navigation_close"
190
- color={theme.colors.background.surface}
191
- size={14}
192
- />
193
- </View>
194
- </TouchableOpacity>
179
+ />
195
180
  );
196
181
  };
197
182
 
@@ -213,7 +198,6 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
213
198
  accessibilityLabel={'title_popup_notify'}
214
199
  // keeps the title clear of the close button on a text only popup
215
200
  style={
216
- isFoldable &&
217
201
  !image &&
218
202
  typeof onIconClose === 'function' &&
219
203
  styles.titleClearClose
@@ -233,7 +217,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
233
217
  }
234
218
  }}
235
219
  >
236
- {description}
220
+ {renderDescription(description)}
237
221
  </Text>
238
222
  ) : (
239
223
  description
@@ -298,29 +282,11 @@ const styles = StyleSheet.create({
298
282
  width: Spacing.S,
299
283
  height: Spacing.S,
300
284
  },
301
- iconCloseContainer: {
302
- position: 'absolute',
303
- top: -Spacing.M,
304
- right: -Spacing.M,
305
- padding: Spacing.XS,
306
- },
307
285
  titleClearClose: { paddingRight: Spacing.XL },
308
- foldableClose: {
286
+ iconClose: {
309
287
  position: 'absolute',
310
288
  top: Spacing.M,
311
289
  right: Spacing.M,
312
- width: FOLDABLE_CLOSE_SIZE,
313
- height: FOLDABLE_CLOSE_SIZE,
314
- alignItems: 'center',
315
- justifyContent: 'center',
316
- },
317
- iconClose: {
318
- width: 20,
319
- height: 20,
320
- alignItems: 'center',
321
- justifyContent: 'center',
322
- borderRadius: Radius.M,
323
- borderWidth: 2,
324
290
  },
325
291
  });
326
292
 
package/Popup/types.ts CHANGED
@@ -20,6 +20,13 @@ export type PopupNotifyProps = {
20
20
  */
21
21
  description?: string | React.ReactNode;
22
22
 
23
+ /**
24
+ * `highlightText`: Optional. List of words/phrases within `description` to be rendered in semibold.
25
+ * Only applies when `description` is a string. Matching is whole-word and case-sensitive;
26
+ * every occurrence of each string is highlighted.
27
+ */
28
+ highlightText?: string[];
29
+
23
30
  /**
24
31
  * `error`: Optional. Represents additional error text to be displayed in the PopupNotify component.
25
32
  */
package/Popup/utils.ts ADDED
@@ -0,0 +1,54 @@
1
+ type HighlightSegment = { text: string; highlight: boolean };
2
+
3
+ const isWordChar = (char?: string) => {
4
+ if (!char) {
5
+ return false;
6
+ }
7
+ const isLetter = char.toLowerCase() !== char.toUpperCase();
8
+ return isLetter || /[0-9_]/.test(char);
9
+ };
10
+
11
+ /**
12
+ * Split `text` into plain/highlighted segments (same matching as Information `highlightText`):
13
+ * whole-word, case-sensitive, every occurrence, longest keyword first.
14
+ * Returns `undefined` when there is nothing to highlight.
15
+ */
16
+ const splitHighlightText = (
17
+ text: string,
18
+ highlightText?: string[],
19
+ ): HighlightSegment[] | undefined => {
20
+ const keywords = highlightText?.filter(keyword => {
21
+ if (typeof keyword !== 'string') {
22
+ return false;
23
+ }
24
+ return keyword.length > 0;
25
+ });
26
+ if (!keywords?.length) {
27
+ return undefined;
28
+ }
29
+ const pattern = [...keywords]
30
+ .sort((a, b) => b.length - a.length)
31
+ .map(keyword => keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
32
+ .join('|');
33
+ const regex = new RegExp(pattern, 'g');
34
+ const segments: HighlightSegment[] = [];
35
+ let lastIndex = 0;
36
+ let match;
37
+ while ((match = regex.exec(text))) {
38
+ const start = match.index;
39
+ const end = start + match[0].length;
40
+ if (isWordChar(text[start - 1]) || isWordChar(text[end])) {
41
+ regex.lastIndex = start + 1;
42
+ continue;
43
+ }
44
+ if (start > lastIndex) {
45
+ segments.push({ text: text.slice(lastIndex, start), highlight: false });
46
+ }
47
+ segments.push({ text: match[0], highlight: true });
48
+ lastIndex = end;
49
+ }
50
+ segments.push({ text: text.slice(lastIndex), highlight: false });
51
+ return segments;
52
+ };
53
+
54
+ export { splitHighlightText };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.168.1-beta.32",
3
+ "version": "0.168.1-beta.34",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},