diff --git a/.maestro/tests/uikit/button-kit.yaml b/.maestro/tests/uikit/button-kit.yaml
new file mode 100644
index 00000000000..409d12f6153
--- /dev/null
+++ b/.maestro/tests/uikit/button-kit.yaml
@@ -0,0 +1,42 @@
+appId: ${APP_ID}
+name: UIKit Button Kit
+onFlowStart:
+ - runFlow: '../../helpers/setup.yaml'
+onFlowComplete:
+ - evalScript: ${output.utils.deleteCreatedUsers()}
+tags:
+ - test-14
+
+---
+- evalScript: ${output.user = output.utils.createUser()}
+- evalScript: ${output.room = output.utils.createRandomRoom(output.user.username, output.user.password)}
+
+- runFlow:
+ file: '../../helpers/login-with-deeplink.yaml'
+ env:
+ USERNAME: ${output.user.username}
+ PASSWORD: ${output.user.password}
+
+- runFlow:
+ file: '../../helpers/navigate-to-room.yaml'
+ env:
+ ROOM: ${output.room.name}
+
+# here send /uikit-test command
+- tapOn:
+ id: message-composer-input
+- inputText:
+ text: '/uikit-test'
+- extendedWaitUntil:
+ visible:
+ id: autocomplete-item-uikit-test
+ timeout: 10000
+- tapOn:
+ id: autocomplete-item-uikit-test
+- tapOn:
+ id: 'message-composer-send'
+- tapOn: Tap Me
+- extendedWaitUntil:
+ visible:
+ text: '.*Button tap received! This reply is private to you*.'
+ timeout: 10000
diff --git a/.sniffler/test-map.json b/.sniffler/test-map.json
index f7575879a9c..714a65fb01b 100644
--- a/.sniffler/test-map.json
+++ b/.sniffler/test-map.json
@@ -341,5 +341,9 @@
"app/sagas/room.js",
"app/sagas/createChannel.js"
]
+ },
+ {
+ "test": ".maestro/tests/uikit/button-kit.yaml",
+ "dependsOn": ["app/containers/UIKit/**"]
}
]
diff --git a/app/containers/UIKit/Actions.tsx b/app/containers/UIKit/Actions.tsx
index 40f328ce812..aef47b96106 100644
--- a/app/containers/UIKit/Actions.tsx
+++ b/app/containers/UIKit/Actions.tsx
@@ -14,6 +14,8 @@ const styles = StyleSheet.create({
});
export const Actions = ({ blockId, appId, elements, parser }: IActions) => {
+ 'use no memo';
+
const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5);
const shouldShowMore = elements && elements.length > 5;
diff --git a/app/containers/UIKit/Button.stories.tsx b/app/containers/UIKit/Button.stories.tsx
new file mode 100644
index 00000000000..b4b51d5bb74
--- /dev/null
+++ b/app/containers/UIKit/Button.stories.tsx
@@ -0,0 +1,20 @@
+import UIKitButton from './Button';
+
+const buttonProps = {
+ title: 'Press me!',
+ type: 'primary' as const,
+ onPress: () => {}
+};
+
+export default {
+ title: 'UIKit/Button',
+ component: UIKitButton
+};
+
+export const PrimaryButton = () => ;
+
+export const SecondaryButton = () => ;
+
+export const LoadingButton = () => ;
+
+export const CustomStyleButton = () => ;
diff --git a/app/containers/UIKit/Button.test.tsx b/app/containers/UIKit/Button.test.tsx
new file mode 100644
index 00000000000..fcb296234d0
--- /dev/null
+++ b/app/containers/UIKit/Button.test.tsx
@@ -0,0 +1,59 @@
+import { fireEvent, render } from '@testing-library/react-native';
+
+import UIKitButton from './Button';
+import * as stories from './Button.stories';
+import { generateSnapshots } from '../../../.rnstorybook/generateSnapshots';
+
+const onPressMock = jest.fn();
+
+const testProps = {
+ title: 'Press me!',
+ onPress: onPressMock
+};
+
+describe('UIKitButtonTests', () => {
+ beforeEach(() => {
+ onPressMock.mockClear();
+ });
+
+ test('rendered with correct title', async () => {
+ const { findByText } = render();
+ const buttonTitle = await findByText(testProps.title);
+ expect(buttonTitle).toBeTruthy();
+ expect(buttonTitle.props.children).toEqual(testProps.title);
+ });
+
+ test('find button using accessibilityLabel', () => {
+ const { getByLabelText } = render();
+ const button = getByLabelText(testProps.title);
+ expect(button).toBeTruthy();
+ });
+
+ test('renders secondary variant with the same title', async () => {
+ const { findByText } = render();
+ const buttonTitle = await findByText(testProps.title);
+ expect(buttonTitle).toBeTruthy();
+ });
+
+ test('title not visible while loading', () => {
+ const { queryByText } = render();
+ const buttonTitle = queryByText(testProps.title);
+ expect(buttonTitle).toBeNull();
+ });
+
+ test('onPress is not triggered while loading', () => {
+ const { getByLabelText } = render();
+ const button = getByLabelText(testProps.title);
+ fireEvent.press(button);
+ expect(onPressMock).not.toHaveBeenCalled();
+ });
+
+ test('should trigger onPress function on button press', () => {
+ const { getByLabelText } = render();
+ const button = getByLabelText(testProps.title);
+ fireEvent.press(button);
+ expect(onPressMock).toHaveBeenCalled();
+ });
+});
+
+generateSnapshots(stories);
diff --git a/app/containers/UIKit/Button.tsx b/app/containers/UIKit/Button.tsx
new file mode 100644
index 00000000000..e16a7d13d95
--- /dev/null
+++ b/app/containers/UIKit/Button.tsx
@@ -0,0 +1,50 @@
+import { type FC } from 'react';
+import { Pressable, StyleSheet, Text, type StyleProp, type ViewStyle } from 'react-native';
+
+import { useTheme } from '../../theme';
+import sharedStyles from '../../views/Styles';
+import ActivityIndicator from '../ActivityIndicator';
+
+const styles = StyleSheet.create({
+ container: {
+ borderRadius: 4,
+ paddingVertical: 14,
+ paddingHorizontal: 16,
+ justifyContent: 'center'
+ },
+ text: {
+ ...sharedStyles.textMedium,
+ ...sharedStyles.textAlignCenter
+ },
+ pressed: {
+ opacity: 0.7
+ }
+});
+
+interface IUIKitButtonProps {
+ title: string;
+ onPress: () => void;
+ type?: 'primary' | 'secondary';
+ loading?: boolean;
+ style?: StyleProp;
+}
+
+const UIKitButton: FC = ({ title, onPress, type = 'primary', loading, style }) => {
+ const { colors } = useTheme();
+ const isPrimary = type === 'primary';
+ const backgroundColor = isPrimary ? colors.buttonBackgroundPrimaryDefault : colors.buttonBackgroundSecondaryDefault;
+ const color = isPrimary ? colors.fontWhite : colors.fontDefault;
+
+ return (
+ [styles.container, { backgroundColor }, style, pressed && styles.pressed]}>
+ {loading ? : {title}}
+
+ );
+};
+
+export default UIKitButton;
diff --git a/app/containers/UIKit/__snapshots__/Button.test.tsx.snap b/app/containers/UIKit/__snapshots__/Button.test.tsx.snap
new file mode 100644
index 00000000000..30b67aed945
--- /dev/null
+++ b/app/containers/UIKit/__snapshots__/Button.test.tsx.snap
@@ -0,0 +1,279 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Story Snapshots: CustomStyleButton should match snapshot 1`] = `
+
+
+ Press me!
+
+
+`;
+
+exports[`Story Snapshots: LoadingButton should match snapshot 1`] = `
+
+
+
+`;
+
+exports[`Story Snapshots: PrimaryButton should match snapshot 1`] = `
+
+
+ Press me!
+
+
+`;
+
+exports[`Story Snapshots: SecondaryButton should match snapshot 1`] = `
+
+
+ Press me!
+
+
+`;
diff --git a/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap b/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap
index a8678d2bfb4..db9fecd1947 100644
--- a/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap
+++ b/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap
@@ -3,63 +3,59 @@
exports[`Story Snapshots: ActionButton should match snapshot 1`] = `
[
-
-
Approve
-
+
,
-
-
Deny
-
+
,
-
-
Deny
-
+
,
-
-
Deny
-
+
,
-
-
Deny
-
+
,
-
-
Deny
-
+
,
-
-
Deny
-
+
,
-
-
button
-
+
`;
diff --git a/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap b/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap
index 491ab67ca3b..9972a5823ea 100644
--- a/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap
+++ b/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap
@@ -1056,63 +1056,59 @@ exports[`Story Snapshots: ModalActionsWithShowMore should match snapshot 1`] = `
}
/>,
-
-
Primary Action
-
+
,
-
-
Secondary
-
+
,
-
-
Danger Action
-
+
,
-
-
Button 4
-
+
,
-
-
Button 5
-
+
,
-
-
Button 6 - Hidden
-
+
,
-
-
Button 7 - Hidden
-
+
,
-
-
Button 8 - Hidden
-
+
,
-
-
Change
-
+
,