Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
e2dbfee
feat: chat kit
kirillzyusko Jan 12, 2026
3036366
docs: documentation scratches
kirillzyusko Jan 20, 2026
f3b3fa2
feat: move `ChatKit` in separate folder and continue all experiments …
kirillzyusko Jan 20, 2026
8dd3f00
feat: move `ChatKit` in separate folder and continue all experiments …
kirillzyusko Jan 20, 2026
2d78ba7
fix: bottom messages not visible on Android
kirillzyusko Jan 20, 2026
c1688ed
feat: try to test paper arch
kirillzyusko Jan 20, 2026
d192f98
fix: mention Android problem
kirillzyusko Jan 20, 2026
a50aa6f
docs: document more findings
kirillzyusko Jan 21, 2026
d418ef6
chore: temp commit
kirillzyusko Jan 23, 2026
5b19821
chore: additional commit
kirillzyusko Jan 23, 2026
390a1b7
feat: add `ClippingScrollView` component
kirillzyusko Jan 27, 2026
d867745
experimental: stash changes for future generations
kirillzyusko Jan 27, 2026
3675c93
chore: track progress
kirillzyusko Jan 27, 2026
287ac6b
fix: match ScrollView styling (use iOS behavior as spec)
kirillzyusko Jan 28, 2026
ea1ca63
feat: complete playground test
kirillzyusko Jan 28, 2026
cdb42d4
fix: update roadmap
kirillzyusko Jan 28, 2026
766d1c8
fix: update todo list
kirillzyusko Jan 28, 2026
737e77e
fix: update roadmap
kirillzyusko Jan 29, 2026
1157bf5
feat: switch to re-usable component
kirillzyusko Jan 29, 2026
a491f19
chore: revert old changes
kirillzyusko Jan 29, 2026
0908328
chore: stash progress for further development
kirillzyusko Jan 29, 2026
4b60b30
fix: use global zustand store for app playground, split code into sma…
kirillzyusko Jan 30, 2026
eed9f30
fix: inverted list
kirillzyusko Jan 31, 2026
b7423cc
chore: cleanup
kirillzyusko Feb 2, 2026
5437fa8
feat: add `keyboardLiftBehavior` prop
kirillzyusko Feb 3, 2026
17e73d5
feat: add more props
kirillzyusko Feb 3, 2026
f106ebe
feat: use modern UI
kirillzyusko Feb 3, 2026
f7dfcf4
feat: inverted support for iOS (except correct scroll)
kirillzyusko Feb 3, 2026
16d3c04
feat: draft support for LegendList
kirillzyusko Feb 3, 2026
7c983c2
fix: inverted FlatList behavior (works well on iOS, except the case o…
kirillzyusko Feb 3, 2026
445c7bb
fix: don't allow to send empty messages
kirillzyusko Feb 3, 2026
b3263a0
chore: temp commit
kirillzyusko Feb 6, 2026
7ac6517
feat: working Android, better iOS performance, conformance to lift mo…
kirillzyusko Feb 12, 2026
6d1a91b
feat: run reanimated unit-tests in src folder
kirillzyusko Feb 13, 2026
ddc6d1b
fix: inverted messages list bug
kirillzyusko Feb 13, 2026
81e2fc3
fix: simplify hook, fix ios + flat list + inverted + hide behavior
kirillzyusko Feb 13, 2026
c8979b5
feat: added unit tests
kirillzyusko Feb 13, 2026
8917805
fix: undesired scroll reset to initial position on Android with non-i…
kirillzyusko Feb 13, 2026
1d0e1eb
feat: add freeze functionality
kirillzyusko Feb 14, 2026
0cd12a1
docs: describe API (as sketch page)
kirillzyusko Feb 14, 2026
865df91
refactor: move mocks into re-usable folder
kirillzyusko Feb 14, 2026
09a541d
feat: port changes to paper arch
kirillzyusko Feb 15, 2026
b1d4448
chore: revert experimental changes
kirillzyusko Feb 15, 2026
8c03e60
feat: frozen sticky view (experimental)
kirillzyusko Feb 15, 2026
0f4ac99
refactor: use less agressive TS nullability force checks
kirillzyusko Feb 15, 2026
3aeaf38
feat: offset property support
kirillzyusko Feb 16, 2026
d88226f
fix: 3rd party libs integration
kirillzyusko Feb 16, 2026
2daa07f
fix: scrolling behavior when first message is visible
kirillzyusko Feb 17, 2026
902b9ca
feat: add support for customizable lift mode
kirillzyusko Feb 17, 2026
00c038f
docs: continue to write docs
kirillzyusko Feb 18, 2026
7a3fd57
Optimised images with calibre/image-actions
github-actions[bot] Feb 18, 2026
e177cf1
feat: add jest mock for new component
kirillzyusko Feb 18, 2026
3007b25
fix: build phase
kirillzyusko Feb 18, 2026
5028f2a
chore: revert experimental changes
kirillzyusko Feb 18, 2026
ae2e84f
chore: revert accident changes
kirillzyusko Feb 18, 2026
09cf15f
docs: more content
kirillzyusko Feb 18, 2026
e4e5506
chore: delete FINDINGS
kirillzyusko Feb 19, 2026
641da2a
fix: web compilation
kirillzyusko Feb 19, 2026
21588f3
fix: re-sync example apps
kirillzyusko Feb 19, 2026
4d17d47
chore: revert `freeze` support for `KeyboardStickyView`
kirillzyusko Feb 19, 2026
6543484
fix: cspell
kirillzyusko Feb 19, 2026
a3ec851
docs: revise the docs structure
kirillzyusko Feb 19, 2026
e665bd2
docs: add snack to example page so that it's interactive
kirillzyusko Feb 19, 2026
f780203
docs: use 25% width
kirillzyusko Feb 19, 2026
5c60c7f
docs: add `offset` prop
kirillzyusko Feb 19, 2026
202ec02
fix: persistent behavior
kirillzyusko Feb 19, 2026
c1e0125
docs: replace assets
kirillzyusko Feb 20, 2026
46c62c8
refactor: use `KeyboardChatScrollView` because `ChatKit` is already r…
kirillzyusko Feb 20, 2026
958c8e3
fix: CI fixes
kirillzyusko Feb 20, 2026
f541c6e
fix: inverted + FlatList + whenAtEnd behavior
kirillzyusko Feb 20, 2026
da04a54
fix: inverted + FlatList + Android + interactive dismissal
kirillzyusko Feb 20, 2026
1838781
feat: count layout pass to cover it in future with e2e tests to assur…
kirillzyusko Feb 20, 2026
ec495a4
chore: remove TODO list
kirillzyusko Feb 20, 2026
feb3221
fix: ref types
kirillzyusko Feb 20, 2026
1a2d326
experimental: enable polyfill for inverted mode (compensate with tran…
kirillzyusko Feb 21, 2026
b8df921
fix: adopt hook for new inverted Android behavior
kirillzyusko Feb 21, 2026
20d8dee
fix: "persistent" mode has a bug. It pushes the content up, but when …
kirillzyusko Feb 21, 2026
f097bc5
fix: "never" - when we are at the end (beginning for inverted case) a…
kirillzyusko Feb 21, 2026
7313ca0
refactor: split implementation into different files, move inline func…
kirillzyusko Feb 21, 2026
51f185d
chore: remove TODO
kirillzyusko Feb 21, 2026
40f31d6
fix: iOS (fabric) + persistent + non-inverted. Don't scroll back when…
kirillzyusko Feb 21, 2026
fa33523
fix: paper arch + iOS + whenAtEnd scrolling to wrong coordinates
kirillzyusko Feb 21, 2026
50742be
fix: ios + non-inverted + never behavior. open keyboard, scroll till …
kirillzyusko Feb 21, 2026
54cb0ab
docs: add one comment that was hard to achieve when you use KeyboardA…
kirillzyusko Feb 21, 2026
6db3247
docs: add missing dot
kirillzyusko Feb 21, 2026
a54d402
docs: add design principles
kirillzyusko Feb 21, 2026
4fa4e31
chore: resolve one TODO
kirillzyusko Feb 22, 2026
a2fb3e1
refactor: move inverted ternary operator to the ViewDecorator, becaus…
kirillzyusko Feb 22, 2026
6e005fe
fix: improve Android behavior
kirillzyusko Feb 22, 2026
7ee1324
fix: improve closing beahvior when content is almost at the end (like…
kirillzyusko Feb 22, 2026
281af75
tests: cover new functionality by unit tests
kirillzyusko Feb 22, 2026
fe1cc90
docs: put real link to the issue
kirillzyusko Feb 23, 2026
f77c498
feat: enable reanimated feature flags
kirillzyusko Feb 23, 2026
a2afeb6
fix: fabric + android bounce
kirillzyusko Feb 23, 2026
43378ff
fix: cspell
kirillzyusko Feb 23, 2026
e758cb6
fix: missing sizes on first render
kirillzyusko Feb 23, 2026
124d7c2
fix: sending functionality
kirillzyusko Feb 23, 2026
adfc006
chore: inverted support for all virtualized lists
kirillzyusko Feb 24, 2026
b1100e4
fix: cspell
kirillzyusko Feb 24, 2026
aca1915
chore: cleanup data
kirillzyusko Feb 24, 2026
46eb1b4
docs: update guide page
kirillzyusko Feb 24, 2026
dc1453d
fix: typo in docs
kirillzyusko Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion FabricExample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"dependencies": {
"@gorhom/bottom-sheet": "5.2.6",
"@legendapp/list": "3.0.0-beta.32",
"@lottiefiles/dotlottie-react": "^0.17.10",
"@react-native-community/blur": "^4.4.1",
"@react-native-masked-view/masked-view": "^0.3.2",
Expand All @@ -36,7 +37,8 @@
"react-native-toast-message": "^2.3.0",
"react-native-web": "^0.21.2",
"react-native-webview": "^13.16.0",
"react-native-worklets": "^0.7.1"
"react-native-worklets": "^0.7.1",
"zustand": "^5.0.10"
},
"devDependencies": {
"@babel/core": "^7.25.2",
Expand Down Expand Up @@ -88,5 +90,10 @@
},
"engines": {
"node": ">=20"
},
"reanimated": {
"staticFeatureFlags": {
"USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS": true
}
}
}
1 change: 1 addition & 0 deletions FabricExample/src/constants/screenNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ export enum ScreenNames {
LIQUID_KEYBOARD = "LIQUID_KEYBOARD",
KEYBOARD_SHARED_TRANSITIONS = "KEYBOARD_SHARED_TRANSITIONS",
KEYBOARD_EXTENDER = "KEYBOARD_EXTENDER",
CHAT_KIT = "CHAT_KIT",
}
15 changes: 15 additions & 0 deletions FabricExample/src/navigation/ExamplesStack/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import InteractiveKeyboard from "../../screens/Examples/InteractiveKeyboard";
import InteractiveKeyboardIOS from "../../screens/Examples/InteractiveKeyboardIOS";
import KeyboardAnimation from "../../screens/Examples/KeyboardAnimation";
import KeyboardAvoidingViewExample from "../../screens/Examples/KeyboardAvoidingView";
import KeyboardChatScrollViewPlayground from "../../screens/Examples/KeyboardChatScrollView";
import KeyboardExtender from "../../screens/Examples/KeyboardExtender";
import KeyboardSharedTransitionExample from "../../screens/Examples/KeyboardSharedTransitions";
import UseKeyboardState from "../../screens/Examples/KeyboardStateHook";
Expand Down Expand Up @@ -54,6 +55,7 @@ export type ExamplesStackParamList = {
[ScreenNames.LIQUID_KEYBOARD]: undefined;
[ScreenNames.KEYBOARD_SHARED_TRANSITIONS]: undefined;
[ScreenNames.KEYBOARD_EXTENDER]: undefined;
[ScreenNames.CHAT_KIT]: undefined;
};

const Stack = createStackNavigator<ExamplesStackParamList>();
Expand Down Expand Up @@ -138,6 +140,14 @@ const options = {
title: "Keyboard Extender",
headerShown: false,
},
[ScreenNames.CHAT_KIT]: {
title: "Chat Kit",
headerTintColor: "#fff",
headerStyle: {
backgroundColor: "#3A3A3C",
},
headerBackTitle: "Back",
},
};

const ExamplesStack = () => (
Expand Down Expand Up @@ -267,6 +277,11 @@ const ExamplesStack = () => (
name={ScreenNames.KEYBOARD_EXTENDER}
options={options[ScreenNames.KEYBOARD_EXTENDER]}
/>
<Stack.Screen
component={KeyboardChatScrollViewPlayground}
name={ScreenNames.CHAT_KIT}
options={options[ScreenNames.CHAT_KIT]}
/>
</Stack.Navigator>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, { forwardRef, useCallback, useMemo, useState } from "react";
import {
type LayoutChangeEvent,
type ScrollViewProps,
StyleSheet,
Text,
} from "react-native";
import { KeyboardChatScrollView } from "react-native-keyboard-controller";
import { useSafeAreaInsets } from "react-native-safe-area-context";

import { useChatConfigStore } from "./store";
import { MARGIN, TEXT_INPUT_HEIGHT } from "./styles";

export type VirtualizedListScrollViewRef = React.ElementRef<
typeof KeyboardChatScrollView
>;

const VirtualizedListScrollView = forwardRef<
VirtualizedListScrollViewRef,
ScrollViewProps
>(({ onLayout: onLayoutProp, ...props }, ref) => {
const [layoutPass, setLayoutPass] = useState(0);
const { bottom } = useSafeAreaInsets();
const chatKitOffset = bottom - MARGIN;

const { inverted, freeze, mode, keyboardLiftBehavior } = useChatConfigStore();

const contentContainerStyle = useMemo(
() => ({ paddingBottom: TEXT_INPUT_HEIGHT + MARGIN }),
[],
);
const invertedContentContainerStyle = useMemo(
() => ({ paddingTop: TEXT_INPUT_HEIGHT + MARGIN }),
[],
);
// on new arch only FlatList supports `inverted` prop
const isInvertedSupported = inverted && mode === "flat" ? inverted : false;
const onLayout = useCallback(
(e: LayoutChangeEvent) => {
setLayoutPass((l) => l + 1);
onLayoutProp?.(e);
},
[onLayoutProp],
);

return (
<>
<KeyboardChatScrollView
ref={ref}
automaticallyAdjustContentInsets={false}
contentContainerStyle={
isInvertedSupported
? invertedContentContainerStyle
: contentContainerStyle
}
contentInsetAdjustmentBehavior="never"
freeze={freeze}
inverted={isInvertedSupported}
keyboardDismissMode="interactive"
keyboardLiftBehavior={keyboardLiftBehavior}
offset={chatKitOffset}
testID="chat.scroll"
onLayout={onLayout}
{...props}
/>
<Text style={styles.counter} testID="layout_passes">
Layout pass: {layoutPass}
</Text>
</>
);
});

const styles = StyleSheet.create({
counter: {
position: "absolute",
color: "white",
top: 0,
padding: 12,
backgroundColor: "#3c3c3c",
},
});

export default VirtualizedListScrollView;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";
import { StyleSheet, Text, View } from "react-native";

import type { MessageProps } from "../../../../components/Message/types";

/**
* Fork of original Message component. Created in order not to break existing e2e tests (requires assets re-generation).
*/
export default function Message({ text, sender }: MessageProps) {
return (
<View style={sender ? styles.senderContainer : styles.recipientContainer}>
<Text style={styles.message}>{text}</Text>
</View>
);
}

const container = {
borderRadius: 10,
padding: 10,
margin: 10,
marginVertical: 5,
maxWidth: "80%" as const,
};

const styles = StyleSheet.create({
senderContainer: {
alignSelf: "flex-end",
backgroundColor: "#F8F8FC",
...container,
},
recipientContainer: {
alignSelf: "flex-start",
backgroundColor: "#64D2FF",
...container,
},
message: {
color: "#000000",
},
});
166 changes: 166 additions & 0 deletions FabricExample/src/screens/Examples/KeyboardChatScrollView/config.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet";
import { useNavigation } from "@react-navigation/native";
import React, { useCallback, useEffect, useRef } from "react";
import { Button, StyleSheet, Text, TouchableOpacity, View } from "react-native";
import { KeyboardController } from "react-native-keyboard-controller";

import Switch from "../../../components/Switch";

import { useChatConfigStore } from "./store";

function ConfigSheet() {
const navigation = useNavigation();
const bottomSheetModalRef = useRef<BottomSheet>(null);
const inverted = useChatConfigStore((state) => state.inverted);
const setInverted = useChatConfigStore((state) => state.setInverted);
const beginning = useChatConfigStore((state) => state.beginning);
const setBeginning = useChatConfigStore((state) => state.setBeginning);
const setFreeze = useChatConfigStore((state) => state.setFreeze);
const mode = useChatConfigStore((state) => state.mode);
const setMode = useChatConfigStore((state) => state.setMode);
const keyboardLiftBehavior = useChatConfigStore(
(state) => state.keyboardLiftBehavior,
);
const nextLiftBehavior = useChatConfigStore(
(state) => state.nextLiftBehavior,
);

const handlePresentModalPress = useCallback(async () => {
setFreeze(true);
requestAnimationFrame(async () => {
await KeyboardController.dismiss({ keepFocus: true });
bottomSheetModalRef.current?.expand();
});
}, [setFreeze]);

const handleSheetChange = useCallback(
(index: number) => {
if (index < 0) {
setFreeze(false);
KeyboardController.setFocusTo("current");
}
},
[setFreeze],
);

useEffect(() => {
navigation.setOptions({
headerRight: () => (
<Text
style={styles.header}
testID="open_bottom_sheet_modal"
onPress={handlePresentModalPress}
>
Open config
</Text>
),
});
}, [navigation]);

return (
<BottomSheet
ref={bottomSheetModalRef}
index={-1}
snapPoints={["50%"]}
onChange={handleSheetChange}
>
<BottomSheetView style={styles.bottomSheetContent}>
<Button
testID="bottom_sheet_close_modal"
title="Close modal"
onPress={() => bottomSheetModalRef.current?.close()}
/>
<View style={styles.switchContainer}>
<Text style={styles.text}>Toggle inverted</Text>
<Switch
testID="bottom_sheet_toggle_inverted_state"
value={inverted}
onChange={() => {
setInverted(!inverted);
}}
/>
</View>
<View style={styles.switchContainer}>
<Text style={styles.text}>Toggle beginning</Text>
<Switch
testID="bottom_sheet_toggle_beginning_state"
value={beginning}
onChange={() => {
setBeginning(!beginning);
}}
/>
</View>
<View style={styles.switchContainer}>
<Text style={styles.text}>Use Scroll View</Text>
<Switch
testID="bottom_sheet_toggle_scroll_view_list_state"
value={mode === "scroll"}
onChange={() => {
setMode("scroll");
}}
/>
</View>
<View style={styles.switchContainer}>
<Text style={styles.text}>Use Flat List</Text>
<Switch
testID="bottom_sheet_toggle_flat_list_state"
value={mode === "flat"}
onChange={() => {
setMode("flat");
}}
/>
</View>
<View style={styles.switchContainer}>
<Text style={styles.text}>Use Flash List</Text>
<Switch
testID="bottom_sheet_toggle_flash_list_state"
value={mode === "flash"}
onChange={() => {
setMode("flash");
}}
/>
</View>
<View style={styles.switchContainer}>
<Text style={styles.text}>Use Legend List</Text>
<Switch
testID="bottom_sheet_toggle_legend_list_state"
value={mode === "legend"}
onChange={() => {
setMode("legend");
}}
/>
</View>
<View style={styles.switchContainer}>
<Text style={styles.text}>Lifting mode</Text>
<TouchableOpacity
testID="bottom_sheet_next_keyboard_lift_behavior"
onPress={nextLiftBehavior}
>
<Text style={styles.text}>{keyboardLiftBehavior}</Text>
</TouchableOpacity>
</View>
</BottomSheetView>
</BottomSheet>
);
}

const styles = StyleSheet.create({
header: {
color: "white",
paddingRight: 12,
},
bottomSheetContent: {
flex: 1,
},
switchContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
margin: 16,
},
text: {
color: "black",
},
});

export default ConfigSheet;
25 changes: 25 additions & 0 deletions FabricExample/src/screens/Examples/KeyboardChatScrollView/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { history } from "../../../components/Message/data";

export const messages = [
...history,
// continue dialog further
{ text: "Do you any other questions?", sender: true },
{ text: "Yes, I have a question about teleport library" },
{ text: "Do you know anything about it?", sender: true },
{
text: "I have seen some techno demos, but haven't tried it uet",
sender: true,
},
{ text: "Yes, I already tried it in my project" },
{
text: "It's pretty cool. We were able to built gallery with share element transitions without any flickering or any other issues",
sender: true,
},
{ text: "So far it looks cool!", sender: true },
{ text: "And seems to be pretty stable", sender: true },
{
text: "This is a literally missing 'move' operation in react/react-native. Before we could only create/delete elements, but now you get power of zero-overhead 'move' operation and it can bring your app performance on the next level and also significantly boost your app UI cause it unlocks the power that we didn't have before",
},
{ text: "Wow! That's awesome!" },
{ text: "Yes! Totally agree!", sender: true },
];
Loading
Loading