@ethora/chat-component-rn 26.5.20 → 26.5.21
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 +40 -0
- package/lib/commonjs/components/MainComponents/ChatHeader.js +14 -1
- package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +1 -1
- package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +6 -0
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +23 -5
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/Modals/ModalHeaderComponent.js +15 -1
- package/lib/commonjs/components/Modals/ModalHeaderComponent.js.map +1 -1
- package/lib/commonjs/components/styled/SendInput.js +28 -2
- package/lib/commonjs/components/styled/SendInput.js.map +1 -1
- package/lib/commonjs/helpers/getElementFont.js +29 -0
- package/lib/commonjs/helpers/getElementFont.js.map +1 -0
- package/lib/commonjs/helpers/headerLayout.js +35 -0
- package/lib/commonjs/helpers/headerLayout.js.map +1 -0
- package/lib/module/components/MainComponents/ChatHeader.js +14 -1
- package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/module/components/Modals/AttachSheet/AttachSheet.js +1 -1
- package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +6 -0
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +24 -6
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/Modals/ModalHeaderComponent.js +15 -1
- package/lib/module/components/Modals/ModalHeaderComponent.js.map +1 -1
- package/lib/module/components/styled/SendInput.js +29 -3
- package/lib/module/components/styled/SendInput.js.map +1 -1
- package/lib/module/helpers/getElementFont.js +24 -0
- package/lib/module/helpers/getElementFont.js.map +1 -0
- package/lib/module/helpers/headerLayout.js +30 -0
- package/lib/module/helpers/headerLayout.js.map +1 -0
- package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts +3 -0
- package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts.map +1 -1
- package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
- package/lib/typescript/helpers/getElementFont.d.ts +13 -0
- package/lib/typescript/helpers/getElementFont.d.ts.map +1 -0
- package/lib/typescript/helpers/headerLayout.d.ts +26 -0
- package/lib/typescript/helpers/headerLayout.d.ts.map +1 -0
- package/lib/typescript/types/types.d.ts +30 -0
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/MainComponents/ChatHeader.tsx +14 -2
- package/src/components/Modals/AttachSheet/AttachSheet.tsx +1 -3
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +10 -4
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +22 -5
- package/src/components/Modals/ModalHeaderComponent.tsx +20 -3
- package/src/components/styled/SendInput.tsx +32 -5
- package/src/helpers/getElementFont.ts +25 -0
- package/src/helpers/headerLayout.ts +30 -0
- package/src/types/types.ts +36 -0
|
@@ -28,6 +28,8 @@ import { useRoomState } from '../../hooks/useRoomState';
|
|
|
28
28
|
import { useChatSettingState } from '../../hooks/useChatSettingState';
|
|
29
29
|
import { View, StyleSheet, Text, Keyboard } from 'react-native';
|
|
30
30
|
import { getIconColor } from '../../helpers/getIconColor';
|
|
31
|
+
import { resolveHeaderHeight } from '../../helpers/headerLayout';
|
|
32
|
+
import { getElementFont } from '../../helpers/getElementFont';
|
|
31
33
|
|
|
32
34
|
interface ChatHeaderProps {
|
|
33
35
|
currentRoom: IRoom;
|
|
@@ -74,9 +76,17 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|
|
74
76
|
[dispatch]
|
|
75
77
|
);
|
|
76
78
|
|
|
79
|
+
// Fixed band height shared with the modal headers. Zero vertical padding
|
|
80
|
+
// here so `height` defines the band exactly (content centers via the
|
|
81
|
+
// styled `align-items: center`); the styled 12px vertical padding would
|
|
82
|
+
// otherwise add on top of it.
|
|
83
|
+
const headerHeight = resolveHeaderHeight(config?.headerLayout?.height);
|
|
84
|
+
|
|
77
85
|
return (
|
|
78
86
|
<>
|
|
79
|
-
<ChatContainerHeader
|
|
87
|
+
<ChatContainerHeader
|
|
88
|
+
style={{ height: headerHeight, paddingTop: 0, paddingBottom: 0 }}
|
|
89
|
+
>
|
|
80
90
|
{handleBackClick && !config?.headerChatMenu ? (
|
|
81
91
|
<View style={styles.leftContainer}>
|
|
82
92
|
<Button
|
|
@@ -159,7 +169,9 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|
|
159
169
|
{composing ? (
|
|
160
170
|
<Composing usersTyping={currentRoom?.composingList} />
|
|
161
171
|
) : config?.disableUserCount ? undefined : (
|
|
162
|
-
<ChatContainerHeaderLabel
|
|
172
|
+
<ChatContainerHeaderLabel
|
|
173
|
+
style={[styles.subLabel, getElementFont(config, 'headerSubtitle')]}
|
|
174
|
+
>
|
|
163
175
|
<Text>{`${currentRoom?.usersCnt} users`}</Text>
|
|
164
176
|
</ChatContainerHeaderLabel>
|
|
165
177
|
)}
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
View,
|
|
13
13
|
} from 'react-native';
|
|
14
14
|
import { CameraIcon, DocumentIcon, MediaIcon } from '../../../assets/icons';
|
|
15
|
-
import { IConfig } from '../../../types/types';
|
|
16
15
|
import { useChatSettingState } from '../../../hooks/useChatSettingState';
|
|
17
16
|
import { chatTextStyle } from '../../../helpers/typography';
|
|
18
17
|
|
|
@@ -23,7 +22,6 @@ interface AttachSheetProps {
|
|
|
23
22
|
onGallery: () => void;
|
|
24
23
|
onDocument: () => void;
|
|
25
24
|
primaryColor?: string;
|
|
26
|
-
/** For `typography.elements.attachSheet*` font overrides. */
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
// Starting translateY for the sheet. Larger than any realistic sheet
|
|
@@ -206,7 +204,7 @@ const AttachSheet: React.FC<AttachSheetProps> = ({
|
|
|
206
204
|
</View>
|
|
207
205
|
<View style={styles.rowText}>
|
|
208
206
|
<Text style={[styles.rowLabel, chatTextStyle(ts?.rowLabel)]}>{label}</Text>
|
|
209
|
-
<Text style={styles.rowHint}>{hint}</Text>
|
|
207
|
+
<Text style={[styles.rowHint, chatTextStyle(ts?.rowHint)]}>{hint}</Text>
|
|
210
208
|
</View>
|
|
211
209
|
</TouchableOpacity>
|
|
212
210
|
))}
|
|
@@ -26,6 +26,7 @@ import { DeleteIcon, MoreIcon, QrIcon } from '../../../assets/icons';
|
|
|
26
26
|
import Switch from '../../MainComponents/Switch';
|
|
27
27
|
import { useChatSettingState } from '../../../hooks/useChatSettingState';
|
|
28
28
|
import { chatTextStyle } from '../../../helpers/typography';
|
|
29
|
+
import { getElementFont } from '../../../helpers/getElementFont';
|
|
29
30
|
import { deleteRoomMember } from '../../../networking/api-requests/rooms.api';
|
|
30
31
|
import { RoomMember } from '../../../types/models/room.model';
|
|
31
32
|
import { setActiveModal, setSelectedUser } from '../../../roomStore/chatSettingsSlice';
|
|
@@ -268,6 +269,7 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
268
269
|
<ModalHeaderComponent
|
|
269
270
|
handleCloseModal={handleCloseModal}
|
|
270
271
|
headerTitle={'Chat Profile'}
|
|
272
|
+
titleStyle={chatTextStyle(config?.typography?.profile?.screenTitle)}
|
|
271
273
|
rightMenu={
|
|
272
274
|
<>
|
|
273
275
|
{activeRoom?.type === 'public' && (
|
|
@@ -320,7 +322,7 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
320
322
|
>
|
|
321
323
|
{activeRoom.title || activeRoom.name}
|
|
322
324
|
</UserName>
|
|
323
|
-
<UserStatus>
|
|
325
|
+
<UserStatus style={getElementFont(config, 'profileStatus')}>
|
|
324
326
|
{activeRoom.usersCnt}{' '}
|
|
325
327
|
{activeRoom.usersCnt > 1 ? 'members' : 'member'}
|
|
326
328
|
</UserStatus>
|
|
@@ -333,13 +335,17 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
333
335
|
)}
|
|
334
336
|
{!config?.disableChatInfo?.disableDescription && (
|
|
335
337
|
<BorderedContainer>
|
|
336
|
-
<LabelData>
|
|
338
|
+
<LabelData style={getElementFont(config, 'profileSectionLabel')}>
|
|
339
|
+
Description
|
|
340
|
+
</LabelData>
|
|
337
341
|
<Label>{activeRoom?.description || '—'}</Label>
|
|
338
342
|
</BorderedContainer>
|
|
339
343
|
)}
|
|
340
344
|
{!config?.disableChatInfo?.disableType && (
|
|
341
345
|
<BorderedContainer>
|
|
342
|
-
<LabelData
|
|
346
|
+
<LabelData style={getElementFont(config, 'profileSectionLabel')}>
|
|
347
|
+
Chat type
|
|
348
|
+
</LabelData>
|
|
343
349
|
<Label>{activeRoom.type || '—'}</Label>
|
|
344
350
|
</BorderedContainer>
|
|
345
351
|
)}
|
|
@@ -428,7 +434,7 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
428
434
|
{user.firstName} {user.lastName}
|
|
429
435
|
</Label>
|
|
430
436
|
{user.last_active && (
|
|
431
|
-
<LabelData>
|
|
437
|
+
<LabelData style={getElementFont(config, 'profileSectionLabel')}>
|
|
432
438
|
{new Date(user.last_active * 1000).toLocaleString()}
|
|
433
439
|
</LabelData>
|
|
434
440
|
)}
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
} from '../styledModalComponents';
|
|
7
7
|
import { SaveIcon } from '../../../assets/icons';
|
|
8
8
|
import ModalHeaderComponent from '../ModalHeaderComponent';
|
|
9
|
+
import { useChatSettingState } from '../../../hooks/useChatSettingState';
|
|
10
|
+
import { chatTextStyle } from '../../../helpers/typography';
|
|
9
11
|
import { useDispatch, useSelector } from 'react-redux';
|
|
10
12
|
import Button from '../../styled/Button';
|
|
11
13
|
import { RootState } from '../../../roomStore';
|
|
@@ -18,6 +20,7 @@ import {
|
|
|
18
20
|
Platform,
|
|
19
21
|
TouchableOpacity,
|
|
20
22
|
StyleSheet,
|
|
23
|
+
Share,
|
|
21
24
|
} from 'react-native';
|
|
22
25
|
import { useVideoPlayer, VideoView } from 'expo-video';
|
|
23
26
|
import { PlayIcon } from '../../../assets/icons';
|
|
@@ -109,6 +112,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|
|
109
112
|
}) => {
|
|
110
113
|
const dispatch = useDispatch();
|
|
111
114
|
const { showToast } = useToast();
|
|
115
|
+
const { config } = useChatSettingState();
|
|
112
116
|
|
|
113
117
|
// Cache the SAF directory the user granted so saving several documents
|
|
114
118
|
// in a row doesn't re-prompt for a folder every time (Android only).
|
|
@@ -204,13 +208,25 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|
|
204
208
|
await FileSystem.writeAsStringAsync(destUri, base64, {
|
|
205
209
|
encoding: FileSystem.EncodingType.Base64,
|
|
206
210
|
});
|
|
211
|
+
|
|
212
|
+
showToast({
|
|
213
|
+
id: Date.now().toString(),
|
|
214
|
+
title: 'Success',
|
|
215
|
+
message: 'Save successful',
|
|
216
|
+
type: 'success',
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
207
219
|
}
|
|
208
220
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
221
|
+
// iOS (and any non-Android): there is no app-writable "Downloads"
|
|
222
|
+
// folder, and MediaLibrary only accepts images/videos — which is why
|
|
223
|
+
// documents previously hit the success toast without ever being
|
|
224
|
+
// saved. Present the system share sheet so the user can "Save to
|
|
225
|
+
// Files", AirDrop, etc. The sheet itself is the confirmation (the
|
|
226
|
+
// user may cancel), so we don't show a "Save successful" toast here.
|
|
227
|
+
await Share.share({
|
|
228
|
+
url: download.uri,
|
|
229
|
+
title: fileName,
|
|
214
230
|
});
|
|
215
231
|
} catch (err) {
|
|
216
232
|
console.error('Error saving file:', err);
|
|
@@ -377,6 +393,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|
|
377
393
|
<ModalHeaderComponent
|
|
378
394
|
handleCloseModal={closeModal}
|
|
379
395
|
headerTitle={'File preview'}
|
|
396
|
+
titleStyle={chatTextStyle(config?.typography?.profile?.screenTitle)}
|
|
380
397
|
rightMenu={
|
|
381
398
|
<>
|
|
382
399
|
<Button onPress={saveClick}>
|
|
@@ -6,12 +6,16 @@ import {
|
|
|
6
6
|
} from './styledModalComponents';
|
|
7
7
|
import { BackIcon, MoreIcon, QrIcon } from '../../assets/icons';
|
|
8
8
|
import Button from '../styled/Button';
|
|
9
|
-
import { Text } from 'react-native';
|
|
9
|
+
import { Text, StyleProp, TextStyle } from 'react-native';
|
|
10
10
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
11
|
+
import { resolveHeaderHeight } from '../../helpers/headerLayout';
|
|
12
|
+
import { useChatSettingState } from '../../hooks/useChatSettingState';
|
|
11
13
|
|
|
12
14
|
interface ModalHeaderComponentProps {
|
|
13
15
|
handleCloseModal?: any;
|
|
14
16
|
headerTitle?: string;
|
|
17
|
+
/** Optional size/weight override for the header title text. */
|
|
18
|
+
titleStyle?: StyleProp<TextStyle>;
|
|
15
19
|
rightMenu?: React.ReactNode;
|
|
16
20
|
leftMenu?: React.ReactNode;
|
|
17
21
|
}
|
|
@@ -19,6 +23,7 @@ interface ModalHeaderComponentProps {
|
|
|
19
23
|
const ModalHeaderComponent: React.FC<ModalHeaderComponentProps> = ({
|
|
20
24
|
handleCloseModal,
|
|
21
25
|
headerTitle,
|
|
26
|
+
titleStyle,
|
|
22
27
|
rightMenu,
|
|
23
28
|
leftMenu,
|
|
24
29
|
}) => {
|
|
@@ -28,15 +33,27 @@ const ModalHeaderComponent: React.FC<ModalHeaderComponentProps> = ({
|
|
|
28
33
|
// mounted by ReduxWrapper, so this reads the live per-device inset
|
|
29
34
|
// (notch / Dynamic Island / nothing).
|
|
30
35
|
const insets = useSafeAreaInsets();
|
|
36
|
+
const { config } = useChatSettingState();
|
|
37
|
+
// Same band height as the in-chat header, sitting BELOW the status-bar
|
|
38
|
+
// inset. Total = inset + band; paddingTop carries the inset and the band
|
|
39
|
+
// is the content area (centered via HeaderContainer's align-items:center),
|
|
40
|
+
// so both headers match in visible height regardless of device notch.
|
|
41
|
+
const band = resolveHeaderHeight(config?.headerLayout?.height);
|
|
31
42
|
return (
|
|
32
|
-
<HeaderContainer
|
|
43
|
+
<HeaderContainer
|
|
44
|
+
style={{
|
|
45
|
+
paddingTop: insets.top,
|
|
46
|
+
paddingBottom: 0,
|
|
47
|
+
height: insets.top + band,
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
33
50
|
<HeaderLeft>
|
|
34
51
|
{leftMenu ? (
|
|
35
52
|
leftMenu
|
|
36
53
|
) : (
|
|
37
54
|
<>
|
|
38
55
|
<Button EndIcon={<BackIcon />} onPress={handleCloseModal} />
|
|
39
|
-
<Text>{headerTitle ?? 'Go back'}</Text>
|
|
56
|
+
<Text style={titleStyle}>{headerTitle ?? 'Go back'}</Text>
|
|
40
57
|
</>
|
|
41
58
|
)}
|
|
42
59
|
</HeaderLeft>
|
|
@@ -13,6 +13,7 @@ import { KeyboardAvoidingView, Platform, View, Text, TouchableOpacity, Alert, Li
|
|
|
13
13
|
import AttachSheet from '../Modals/AttachSheet/AttachSheet';
|
|
14
14
|
import { MediaFilePreview } from './MediaFilePreview';
|
|
15
15
|
import { getIconColor } from '../../helpers/getIconColor';
|
|
16
|
+
import { getElementFont } from '../../helpers/getElementFont';
|
|
16
17
|
import * as ImagePicker from 'expo-image-picker';
|
|
17
18
|
import * as DocumentPicker from 'expo-document-picker';
|
|
18
19
|
import * as ImageManipulator from 'expo-image-manipulator';
|
|
@@ -120,6 +121,13 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
120
121
|
// (versus duplicating the send with stale closure state).
|
|
121
122
|
const sendingRef = useRef(false);
|
|
122
123
|
|
|
124
|
+
// Re-entry guard for the document picker. expo-document-picker keeps a
|
|
125
|
+
// single native `pickingContext`; invoking getDocumentAsync again while a
|
|
126
|
+
// pick is still in flight throws "Different document picking in progress"
|
|
127
|
+
// (iOS, bug #4). A double-fire happens on a fast double-tap of the
|
|
128
|
+
// "Document" row or a dev-mode double-run of the sheet's close callback.
|
|
129
|
+
const pickingDocRef = useRef(false);
|
|
130
|
+
|
|
123
131
|
const handleFileSelect = (files: MediaFile[]) => {
|
|
124
132
|
// Enforce the upload size cap before anything reaches the network.
|
|
125
133
|
// The backend rejects oversized bodies with HTTP 413; catching it
|
|
@@ -237,7 +245,21 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
237
245
|
};
|
|
238
246
|
|
|
239
247
|
const handleFileSelection = async () => {
|
|
248
|
+
// Drop overlapping invocations — a second getDocumentAsync while the
|
|
249
|
+
// first is still presented throws PickingInProgressException on iOS.
|
|
250
|
+
if (pickingDocRef.current) {return;}
|
|
251
|
+
pickingDocRef.current = true;
|
|
240
252
|
try {
|
|
253
|
+
// Let the attach-sheet's modal finish its NATIVE dismissal before we
|
|
254
|
+
// present the picker. The sheet fires this handler right after its
|
|
255
|
+
// slide-out animation, but the underlying RN Modal view-controller
|
|
256
|
+
// dismissal is async — presenting the document picker over a still-
|
|
257
|
+
// dismissing modal makes iOS tear it down without a cancel callback,
|
|
258
|
+
// which strands expo-document-picker's pickingContext and makes every
|
|
259
|
+
// subsequent open throw "Different document picking in progress".
|
|
260
|
+
if (Platform.OS === 'ios') {
|
|
261
|
+
await new Promise((resolve) => setTimeout(resolve, 350));
|
|
262
|
+
}
|
|
241
263
|
const result = await DocumentPicker.getDocumentAsync({
|
|
242
264
|
type: '*/*',
|
|
243
265
|
multiple: false,
|
|
@@ -255,6 +277,8 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
255
277
|
]);
|
|
256
278
|
} catch (err) {
|
|
257
279
|
console.error('DocumentPicker error:', err);
|
|
280
|
+
} finally {
|
|
281
|
+
pickingDocRef.current = false;
|
|
258
282
|
}
|
|
259
283
|
};
|
|
260
284
|
|
|
@@ -520,11 +544,14 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
520
544
|
// the text had dropped). minHeight keeps the 40px tap
|
|
521
545
|
// target; the row's align-items:center vertically centres
|
|
522
546
|
// the content-sized input.
|
|
523
|
-
style={
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
547
|
+
style={[
|
|
548
|
+
{
|
|
549
|
+
minHeight: config?.typography?.input?.minHeight ?? 40,
|
|
550
|
+
maxHeight: config?.typography?.input?.maxHeight ?? 120,
|
|
551
|
+
flex: 1,
|
|
552
|
+
},
|
|
553
|
+
getElementFont(config, 'inputText'),
|
|
554
|
+
]}
|
|
528
555
|
/>
|
|
529
556
|
</>
|
|
530
557
|
)}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TextStyle } from 'react-native';
|
|
2
|
+
import type { IConfig, TypographyConfig } from '../types/types';
|
|
3
|
+
|
|
4
|
+
type ElementKey = keyof NonNullable<TypographyConfig['elements']>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Per-element font override from `config.typography.elements`, as an RN
|
|
8
|
+
* style object suitable for appending to a component's `style` (so it
|
|
9
|
+
* wins over the built-in size/weight but touches nothing else).
|
|
10
|
+
* Returns undefined when the element isn't configured — appending
|
|
11
|
+
* undefined to a style array is a no-op.
|
|
12
|
+
*/
|
|
13
|
+
export const getElementFont = (
|
|
14
|
+
config: IConfig | undefined,
|
|
15
|
+
element: ElementKey
|
|
16
|
+
): TextStyle | undefined => {
|
|
17
|
+
const override = config?.typography?.elements?.[element];
|
|
18
|
+
if (!override) {return undefined;}
|
|
19
|
+
const style: TextStyle = {};
|
|
20
|
+
if (override.fontSize != null) {style.fontSize = override.fontSize;}
|
|
21
|
+
if (override.fontWeight != null) {style.fontWeight = override.fontWeight;}
|
|
22
|
+
return style.fontSize != null || style.fontWeight != null
|
|
23
|
+
? style
|
|
24
|
+
: undefined;
|
|
25
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** @format */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default (and minimum) height in px of the header content band — the bar
|
|
5
|
+
* itself, NOT counting the status-bar safe-area inset that sits above it.
|
|
6
|
+
*
|
|
7
|
+
* This single value is the "common denominator" shared by BOTH headers:
|
|
8
|
+
* • the in-chat header (ChatHeader / ChatContainerHeader), and
|
|
9
|
+
* • the full-screen modal header (ModalHeaderComponent / HeaderContainer)
|
|
10
|
+
* so they render at the same visible height below the status bar.
|
|
11
|
+
*
|
|
12
|
+
* It's also the floor: 64px comfortably fits the 40px avatar / back button
|
|
13
|
+
* plus breathing room. A smaller band clips that content, so configured
|
|
14
|
+
* values at or below this are ignored (see `resolveHeaderHeight`).
|
|
15
|
+
*/
|
|
16
|
+
export const DEFAULT_HEADER_HEIGHT = 64;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the effective header band height from an optional config override.
|
|
20
|
+
*
|
|
21
|
+
* The override can only make the header TALLER, never shorter than the
|
|
22
|
+
* default — a value <= DEFAULT_HEADER_HEIGHT (or non-numeric) is ignored and
|
|
23
|
+
* the default is used, so a too-small number (e.g. 10) can't collapse the
|
|
24
|
+
* bar and break its layout. Only a value strictly greater than the default
|
|
25
|
+
* takes effect.
|
|
26
|
+
*/
|
|
27
|
+
export const resolveHeaderHeight = (height?: number): number =>
|
|
28
|
+
typeof height === 'number' && height > DEFAULT_HEADER_HEIGHT
|
|
29
|
+
? height
|
|
30
|
+
: DEFAULT_HEADER_HEIGHT;
|
package/src/types/types.ts
CHANGED
|
@@ -300,6 +300,8 @@ export interface TypographyConfig {
|
|
|
300
300
|
};
|
|
301
301
|
/** Chat profile screen (opens when tapping the room name in the header). */
|
|
302
302
|
profile?: {
|
|
303
|
+
/** The "Chat Profile" bar title at the very top of the profile screen. Default ~system (small). */
|
|
304
|
+
screenTitle?: ChatTextStyle;
|
|
303
305
|
/** Big room-name title at the top of the profile screen. Default 24 / "400". */
|
|
304
306
|
title?: ChatTextStyle;
|
|
305
307
|
/** Member name rows. Default 16 / "600". */
|
|
@@ -311,9 +313,29 @@ export interface TypographyConfig {
|
|
|
311
313
|
title?: ChatTextStyle;
|
|
312
314
|
/** Row labels (Camera / Gallery / Document). Default 16 / "500". */
|
|
313
315
|
rowLabel?: ChatTextStyle;
|
|
316
|
+
/** Grey hint under each row. Default 12. */
|
|
317
|
+
rowHint?: ChatTextStyle;
|
|
314
318
|
/** Cancel button. Default 16 / "600". */
|
|
315
319
|
cancelButton?: ChatTextStyle;
|
|
316
320
|
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Generic per-element font overrides addressed by key, applied via the
|
|
324
|
+
* `getElementFont` helper as the highest-priority style layer. These target
|
|
325
|
+
* smaller text elements that don't have a dedicated key above:
|
|
326
|
+
* - `headerSubtitle` — the "N users" / typing line in the chat header
|
|
327
|
+
* - `inputText` — the message composer input (overrides `input`)
|
|
328
|
+
* - `profileStatus` — the "N members" line on the profile screen
|
|
329
|
+
* - `profileSectionLabel` — profile field labels ("Description", "Chat
|
|
330
|
+
* type") and the member last-active line
|
|
331
|
+
* When both this and a dedicated key target the same element, this wins.
|
|
332
|
+
*/
|
|
333
|
+
elements?: Partial<
|
|
334
|
+
Record<
|
|
335
|
+
'headerSubtitle' | 'inputText' | 'profileStatus' | 'profileSectionLabel',
|
|
336
|
+
ChatTextStyle
|
|
337
|
+
>
|
|
338
|
+
>;
|
|
317
339
|
}
|
|
318
340
|
|
|
319
341
|
export interface IConfig {
|
|
@@ -358,6 +380,20 @@ export interface IConfig {
|
|
|
358
380
|
headerLogo?: string | React.ReactElement;
|
|
359
381
|
|
|
360
382
|
// ----- header / nav -----
|
|
383
|
+
/**
|
|
384
|
+
* Sizing for BOTH headers (the in-chat header and the full-screen modal
|
|
385
|
+
* headers like Chat Profile), kept in sync so they render at the same
|
|
386
|
+
* visible height.
|
|
387
|
+
*/
|
|
388
|
+
headerLayout?: {
|
|
389
|
+
/**
|
|
390
|
+
* Height in px of the header bar (the band below the status bar).
|
|
391
|
+
* Default 64. Acts as a floor too: values at or below the default are
|
|
392
|
+
* ignored so a too-small number can't collapse the bar — only a larger
|
|
393
|
+
* value takes effect.
|
|
394
|
+
*/
|
|
395
|
+
height?: number;
|
|
396
|
+
};
|
|
361
397
|
disableHeader?: boolean;
|
|
362
398
|
disableMedia?: boolean;
|
|
363
399
|
/**
|