@poolse/react-native 2.0.5 → 2.0.7

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 CHANGED
@@ -25,6 +25,39 @@ versions compatible with your installed Expo SDK automatically.
25
25
 
26
26
  ## Quick start
27
27
 
28
+ The drop-in inbox — list + detail with a slide animation between them, edge-swipe back, plus built-in pickers when you provide a user directory:
29
+
30
+ ```tsx
31
+ import { PoolseProvider } from '@poolse/react';
32
+ import { PoolseInbox, PoolseTheme } from '@poolse/react-native';
33
+
34
+ const users = [
35
+ { externalId: 'alice', name: 'Alice', avatarUrl: 'https://…' },
36
+ { externalId: 'bob', name: 'Bob', avatarUrl: 'https://…' },
37
+ ];
38
+
39
+ export default function InboxScreen() {
40
+ return (
41
+ <PoolseProvider config={{ getToken, userResolver }}>
42
+ <PoolseTheme>
43
+ <PoolseInbox
44
+ title="Chats"
45
+ users={users}
46
+ labelFor={(externalId) =>
47
+ users.find((u) => u.externalId === externalId)?.name ?? externalId
48
+ }
49
+ avatarFor={(externalId) =>
50
+ users.find((u) => u.externalId === externalId)?.avatarUrl ?? null
51
+ }
52
+ />
53
+ </PoolseTheme>
54
+ </PoolseProvider>
55
+ );
56
+ }
57
+ ```
58
+
59
+ If you'd rather render a single conversation (your app already owns the navigation), use `<ConversationView>` directly:
60
+
28
61
  ```tsx
29
62
  import { PoolseProvider } from '@poolse/react';
30
63
  import { PoolseTheme, ConversationView } from '@poolse/react-native';
@@ -40,33 +73,32 @@ export default function ChatScreen({ conversationId }: { conversationId: string
40
73
  }
41
74
  ```
42
75
 
43
- The same `<PoolseProvider>` you use on web. Wrap it once at the app
44
- root if you have multiple chat screens. Drop the `<PoolseTheme>`
45
- wrapper if you don't need to override the brand defaults — every
46
- component has a fallback theme baked in.
76
+ The same `<PoolseProvider>` you use on web. Wrap it once at the app root if you have multiple chat screens. Drop the `<PoolseTheme>` wrapper if you don't need to override the brand defaults — every component has a fallback theme baked in.
47
77
 
48
78
  ## What ships
49
79
 
50
- | Component | What it does |
51
- | --------------------------------------------- | ----------------------------------------------------------------------------------------------- |
52
- | `<ConversationView>` | Composed chat surface — message list + composer + typing strip. Feature flags identical to web. |
53
- | `<MessageBubble>` / `<EditableMessageBubble>` | Single message render — body, meta, read receipt, quoted card, in-bubble attachments. |
54
- | `<MessageRow>` | Bubble + alignment + actions slot. |
55
- | `<MessageList>` | Inverted `FlatList` wrapper — stick-to-bottom, hold-position, auto-load-more. |
56
- | `<MessageComposer>` | Text input with auto-grow, send button, attach button, quote chip. |
57
- | `<MessageActions>` | Long-press menu (reply / quote / edit / delete / copy / react). |
58
- | `<TypingIndicator>` | Animated three-dot indicator + labels. |
59
- | `<ThreadView>` | Modal screen for thread replies. |
60
- | `<ConversationList>` | `FlatList` of conversation rows with unread badges. |
61
- | `<MemberList>` | Member roster with avatars + remove action. |
62
- | `<MentionInput>` | `TextInput` + autocomplete popover. |
63
- | `<ReactionStrip>` / `<ReactionPicker>` | Reaction summary + emoji picker sheet. |
64
- | `<AttachmentPreview>` | Image or file card. |
65
- | `<AttachmentPicker>` | Wraps `expo-image-picker` + `expo-document-picker`. |
66
- | `<UploadQueueStrip>` | Pending uploads with cancel. |
67
- | `<UserName>` / `useDisplayName(externalId)` | Display name resolution via the SDK's `userResolver`. |
68
- | `<Avatar>` | Initials + URL fallback, stable color per user. |
69
- | `<PoolseIcon>` / `<PoolseLogo>` | Brand chrome via `react-native-svg`. |
80
+ | Component | What it does |
81
+ | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
82
+ | `<PoolseInbox>` | Full inbox shell — list + detail with an iOS-style slide animation between them, left-edge-swipe-back to return to the list, built-in `New chat` / `New group` picker sheets when `users` is provided. Imperative ref API: `open`, `openDirect`, `openGroup`, `promptNewChat`, `promptNewGroup`, `close`. |
83
+ | `<ChatHeader>` | Title bar — direct chats resolve the other member's name + avatar via `labelFor` / `avatarFor`, groups show member count, presence-driven green dot. Optional `onBack` button. |
84
+ | `<ConversationView>` | Composed chat surface — message list + composer + typing strip + thread modal. Single `KeyboardAvoidingView` covers the whole thing so the composer stays above the keyboard on both platforms. |
85
+ | `<ThreadView>` | Bottom-sheet modal for thread replies. Reactions + attachments + pull-down-to-close via a 60-pt header drag strip. Composer reuses the same `UploadProvider` queue as the picker. |
86
+ | `<GroupDetailsSheet>` | Bottom sheet showing a group's name, avatar, and member roster with presence. Opened from `<ChatHeader>`'s members button. |
87
+ | `<MessageBubble>` / `<EditableMessageBubble>` | Single message render — body, meta, read receipt, quoted card, in-bubble attachments. |
88
+ | `<MessageRow>` | Bubble + alignment + swipe-to-reply (capture-phase, 1:1 finger follow, sqrt-damped past 50pt threshold). |
89
+ | `<MessageList>` | Inverted `FlatList` wrapper — stick-to-bottom, hold-position, auto-load-more. |
90
+ | `<MessageComposer>` | Text input with auto-grow, send button, attach button, quote chip. |
91
+ | `<MessageActions>` | Long-press menu (reply / quote / edit / delete / copy / react). |
92
+ | `<TypingIndicator>` | Animated three-dot indicator + labels. |
93
+ | `<ConversationList>` | `FlatList` of conversation rows. Direct rows pull the other member's name + avatar via `labelFor` / `avatarFor`; preview reads `last_message_preview` updated in realtime by the SDK's `conversation:updated` subscription. Unread badges. |
94
+ | `<MemberList>` | Member roster with avatars + remove action. |
95
+ | `<MentionInput>` | `TextInput` + autocomplete popover. |
96
+ | `<ReactionStrip>` / `<ReactionPicker>` | Reaction summary + emoji picker sheet. |
97
+ | `<AttachmentPreview>` / `<AttachmentPicker>` | Image or file card. Picker wraps `expo-image-picker` + `expo-document-picker`. |
98
+ | `<UploadQueueStrip>` | Pending uploads with cancel. |
99
+ | `<UserName>` / `useDisplayName(externalId)` | Display name resolution via the SDK's `userResolver`. |
100
+ | `<Avatar>` | Initials + URL fallback, stable color per user. |
101
+ | `<PoolseIcon>` / `<PoolseLogo>` | Brand chrome via `react-native-svg`. |
70
102
 
71
103
  ## Theming
72
104
 
@@ -141,8 +173,18 @@ The wire still carries `user_id` (the poolse uuid) alongside
141
173
  | Mention picker | Inline dropdown | Modal sheet |
142
174
  | Attachment picker | `<input type=file>` | `expo-image-picker` + `expo-document-picker` |
143
175
  | Message list | Reversed CSS flexbox | Inverted `FlatList` |
144
- | Markdown | `react-markdown` | Plain text in 0.1 (markdown coming in 0.2) |
176
+ | Markdown | `react-markdown` | Plain text |
177
+
178
+ Everything else — feature flags, prop names, callback shapes, hook signatures — is intentionally identical so a web codebase ports across with mostly mechanical changes.
179
+
180
+ ## Links
181
+
182
+ - Docs: [poolse.dev/docs/sdk/react-native](https://poolse.dev/docs/sdk/react-native)
183
+ - Source: [github.com/poolse-hq/js-sdk](https://github.com/poolse-hq/js-sdk/tree/main/packages/react-native)
184
+ - Web sibling: [`@poolse/react-ui`](https://www.npmjs.com/package/@poolse/react-ui)
185
+ - Hooks (used internally): [`@poolse/react`](https://www.npmjs.com/package/@poolse/react)
186
+ - Core client: [`@poolse/sdk`](https://www.npmjs.com/package/@poolse/sdk)
187
+
188
+ ## License
145
189
 
146
- Everything else — feature flags, prop names, callback shapes,
147
- hook signatures — is intentionally identical so a web codebase
148
- ports across with mostly mechanical changes.
190
+ MIT
package/dist/index.cjs CHANGED
@@ -511,18 +511,16 @@ function MemberList({
511
511
  style: { backgroundColor: theme.colors.paper },
512
512
  renderItem: ({ item }) => {
513
513
  if (renderItem) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderItem(item) });
514
- const name = labelFor?.(item.external_id) ?? item.external_id;
515
- const avatarUrl = avatarFor?.(item.external_id) ?? null;
516
514
  const isOnline = effectiveOnline.has(item.external_id);
517
515
  const showRemove = canRemove?.(item) ?? false;
518
516
  return /* @__PURE__ */ jsxRuntime.jsx(
519
517
  MemberRow,
520
518
  {
521
- name,
522
519
  externalId: item.external_id,
523
- avatarUrl,
524
520
  role: item.role,
525
521
  online: isOnline,
522
+ ...labelFor ? { labelFor } : {},
523
+ ...avatarFor ? { avatarFor } : {},
526
524
  ...showRemove ? { onRemove: () => removeMember(item.external_id) } : {}
527
525
  }
528
526
  );
@@ -531,14 +529,17 @@ function MemberList({
531
529
  );
532
530
  }
533
531
  function MemberRow({
534
- name,
535
532
  externalId,
536
- avatarUrl,
537
533
  role,
538
534
  online,
535
+ labelFor,
536
+ avatarFor,
539
537
  onRemove
540
538
  }) {
541
539
  const theme = usePoolseTheme();
540
+ const resolved = react$1.useUser(externalId);
541
+ const name = labelFor?.(externalId) ?? resolved.profile?.displayName ?? externalId;
542
+ const avatarUrl = avatarFor?.(externalId) ?? resolved.profile?.avatarUrl ?? null;
542
543
  return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.row, children: [
543
544
  /* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: avatarUrl, name, seed: externalId, online, size: "md" }),
544
545
  /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.body, children: [
@@ -2872,8 +2873,9 @@ function MessageRow({
2872
2873
  });
2873
2874
  const showAvatarSlot = showAvatar && !isSelf;
2874
2875
  const showAvatarRender = showAvatarSlot && (groupPosition === "last" || groupPosition === "standalone");
2875
- const avatarUrl = avatarFor && msg.sender_external_id ? avatarFor(msg.sender_external_id) : null;
2876
- const senderLabel = msg.sender_external_id && labelFor ? labelFor(msg.sender_external_id) : msg.sender_external_id ?? null;
2876
+ const rowSender = react$1.useUser(showAvatarSlot ? msg.sender_external_id : null);
2877
+ const avatarUrl = msg.sender_external_id ? avatarFor?.(msg.sender_external_id) ?? rowSender.profile?.avatarUrl ?? null : null;
2878
+ const senderLabel = msg.sender_external_id ? labelFor?.(msg.sender_external_id) ?? rowSender.profile?.displayName ?? msg.sender_external_id : null;
2877
2879
  return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles17.row, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: [
2878
2880
  showAvatarSlot ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.avatarSlot, children: showAvatarRender ? /* @__PURE__ */ jsxRuntime.jsx(
2879
2881
  Avatar,
@@ -4266,8 +4268,9 @@ function DefaultConversationRow({
4266
4268
  const theme = usePoolseTheme();
4267
4269
  const isDirect = conv.type === "direct";
4268
4270
  const otherExtId = isDirect && meExternalId ? (conv.member_external_ids ?? []).find((x) => x !== meExternalId) : null;
4269
- const title = isDirect ? otherExtId ? labelFor?.(otherExtId) ?? otherExtId : "Direct chat" : conv.name ?? "Untitled group";
4270
- const avatarUrl = isDirect && otherExtId ? avatarFor?.(otherExtId) ?? null : null;
4271
+ const resolvedOther = react$1.useUser(isDirect ? otherExtId ?? null : null);
4272
+ const title = isDirect ? otherExtId ? labelFor?.(otherExtId) ?? resolvedOther.profile?.displayName ?? otherExtId : "Direct chat" : conv.name ?? "Untitled group";
4273
+ const avatarUrl = isDirect && otherExtId ? avatarFor?.(otherExtId) ?? resolvedOther.profile?.avatarUrl ?? null : null;
4271
4274
  const avatarSeed = isDirect ? otherExtId ?? conv.id : conv.id;
4272
4275
  return /* @__PURE__ */ jsxRuntime.jsxs(
4273
4276
  reactNative.Pressable,