@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.
Files changed (56) hide show
  1. package/README.md +40 -0
  2. package/lib/commonjs/components/MainComponents/ChatHeader.js +14 -1
  3. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  4. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +1 -1
  5. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  6. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +6 -0
  7. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  8. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +23 -5
  9. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  10. package/lib/commonjs/components/Modals/ModalHeaderComponent.js +15 -1
  11. package/lib/commonjs/components/Modals/ModalHeaderComponent.js.map +1 -1
  12. package/lib/commonjs/components/styled/SendInput.js +28 -2
  13. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  14. package/lib/commonjs/helpers/getElementFont.js +29 -0
  15. package/lib/commonjs/helpers/getElementFont.js.map +1 -0
  16. package/lib/commonjs/helpers/headerLayout.js +35 -0
  17. package/lib/commonjs/helpers/headerLayout.js.map +1 -0
  18. package/lib/module/components/MainComponents/ChatHeader.js +14 -1
  19. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  20. package/lib/module/components/Modals/AttachSheet/AttachSheet.js +1 -1
  21. package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  22. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +6 -0
  23. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  24. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +24 -6
  25. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  26. package/lib/module/components/Modals/ModalHeaderComponent.js +15 -1
  27. package/lib/module/components/Modals/ModalHeaderComponent.js.map +1 -1
  28. package/lib/module/components/styled/SendInput.js +29 -3
  29. package/lib/module/components/styled/SendInput.js.map +1 -1
  30. package/lib/module/helpers/getElementFont.js +24 -0
  31. package/lib/module/helpers/getElementFont.js.map +1 -0
  32. package/lib/module/helpers/headerLayout.js +30 -0
  33. package/lib/module/helpers/headerLayout.js.map +1 -0
  34. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  35. package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
  36. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  37. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  38. package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts +3 -0
  39. package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts.map +1 -1
  40. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  41. package/lib/typescript/helpers/getElementFont.d.ts +13 -0
  42. package/lib/typescript/helpers/getElementFont.d.ts.map +1 -0
  43. package/lib/typescript/helpers/headerLayout.d.ts +26 -0
  44. package/lib/typescript/helpers/headerLayout.d.ts.map +1 -0
  45. package/lib/typescript/types/types.d.ts +30 -0
  46. package/lib/typescript/types/types.d.ts.map +1 -1
  47. package/package.json +1 -1
  48. package/src/components/MainComponents/ChatHeader.tsx +14 -2
  49. package/src/components/Modals/AttachSheet/AttachSheet.tsx +1 -3
  50. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +10 -4
  51. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +22 -5
  52. package/src/components/Modals/ModalHeaderComponent.tsx +20 -3
  53. package/src/components/styled/SendInput.tsx +32 -5
  54. package/src/helpers/getElementFont.ts +25 -0
  55. package/src/helpers/headerLayout.ts +30 -0
  56. 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 style={styles.subLabel}>
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>Description</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>Chat type</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
- showToast({
210
- id: Date.now().toString(),
211
- title: 'Success',
212
- message: 'Save successful',
213
- type: 'success',
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 style={{ paddingTop: insets.top + 8 }}>
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
- minHeight: config?.typography?.input?.minHeight ?? 40,
525
- maxHeight: config?.typography?.input?.maxHeight ?? 120,
526
- flex: 1,
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;
@@ -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
  /**