Skip to content

Commit f41a013

Browse files
authored
feat(rna): add top level component slots (#3040)
1 parent 247cc1f commit f41a013

File tree

8 files changed

+134
-4
lines changed

8 files changed

+134
-4
lines changed

examples/react-native/src/App/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React from 'react';
22

33
// import Example from '../features/Authenticator/Demo/Example';
4-
import Example from '../features/Authenticator/Styles/Example';
4+
import Example from '../features/Authenticator/Slots/Example';
5+
// import Example from '../features/Authenticator/Styles/Example';
6+
57
// import { Demo as InAppDemo } from '../features/InAppMessaging';
68

79
const App = () => {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react';
2+
import { StyleSheet, Text, View } from 'react-native';
3+
4+
import {
5+
Authenticator,
6+
useAuthenticator,
7+
useTheme,
8+
} from '@aws-amplify/ui-react-native';
9+
import { Amplify } from 'aws-amplify';
10+
11+
import { Button } from '../../../ui';
12+
13+
Amplify.configure({});
14+
15+
const MyHeader = () => {
16+
const {
17+
tokens: { space, fontSizes },
18+
} = useTheme();
19+
return (
20+
<View>
21+
<Text style={{ fontSize: fontSizes.xxxl, padding: space.xl }}>
22+
My Header
23+
</Text>
24+
</View>
25+
);
26+
};
27+
28+
function SignOutButton() {
29+
const { signOut } = useAuthenticator();
30+
return <Button onPress={signOut}>Sign Out</Button>;
31+
}
32+
33+
function App() {
34+
const {
35+
tokens: { colors },
36+
} = useTheme();
37+
38+
return (
39+
<Authenticator.Provider>
40+
<Authenticator
41+
Container={(props) => (
42+
<Authenticator.Container
43+
{...props}
44+
style={{ backgroundColor: colors.pink[20] }}
45+
/>
46+
)}
47+
Header={MyHeader}
48+
>
49+
<View style={style.container}>
50+
<SignOutButton />
51+
</View>
52+
</Authenticator>
53+
</Authenticator.Provider>
54+
);
55+
}
56+
57+
const style = StyleSheet.create({
58+
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
59+
});
60+
61+
export default App;

packages/react-native/src/Authenticator/Authenticator.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ const routePropSelector = ({
5353
function Authenticator({
5454
children,
5555
components: overrides,
56+
Container = DefaultContainer,
57+
Footer,
58+
Header,
5659
...options
5760
}: AuthenticatorProps): JSX.Element | null {
5861
useAuthenticatorInitMachine(options);
@@ -75,16 +78,19 @@ function Authenticator({
7578

7679
return (
7780
<SafeAreaProvider>
78-
<DefaultContainer>
81+
<Container>
82+
{Header ? <Header /> : null}
7983
<InnerContainer>
8084
<Component {...props} fields={typedFields} />
8185
</InnerContainer>
82-
</DefaultContainer>
86+
{Footer ? <Footer /> : null}
87+
</Container>
8388
</SafeAreaProvider>
8489
);
8590
}
8691

8792
// assign slot components
93+
Authenticator.Container = DefaultContainer;
8894
Authenticator.Provider = Provider;
8995
Authenticator.ConfirmResetPassword = ConfirmResetPassword;
9096
Authenticator.ConfirmSignIn = ConfirmSignIn;

packages/react-native/src/Authenticator/__tests__/Authenticator.spec.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { Text } from 'react-native';
2+
import { Text, View } from 'react-native';
33
import { render } from '@testing-library/react-native';
44

55
import * as UIReactCoreModule from '@aws-amplify/ui-react-core';
@@ -10,18 +10,35 @@ import {
1010

1111
import { Authenticator } from '..';
1212

13+
jest.mock('aws-amplify');
1314
jest.mock('@aws-amplify/ui-react-core');
1415

1516
const CHILD_TEST_ID = 'child-test-id';
1617
const CHILD_CONTENT = 'Test Children';
1718

19+
const CONTAINER_TEST_ID = 'container-test-id';
20+
const FOOTER_TEST_ID = 'footer-test-id';
21+
const HEADER_TEST_ID = 'header-test-id';
22+
1823
function TestChildren() {
1924
return <Text testID={CHILD_TEST_ID}>{CHILD_CONTENT}</Text>;
2025
}
2126
const TestComponent = () => {
2227
return null;
2328
};
2429

30+
function Container({ children }: { children: React.ReactNode }) {
31+
return <View testID={CONTAINER_TEST_ID}>{children}</View>;
32+
}
33+
34+
function Footer() {
35+
return <View testID={FOOTER_TEST_ID} />;
36+
}
37+
38+
function Header() {
39+
return <View testID={HEADER_TEST_ID} />;
40+
}
41+
2542
const useAuthenticatorInitMachineSpy = jest.spyOn(
2643
UIReactCoreModule,
2744
'useAuthenticatorInitMachine'
@@ -98,4 +115,20 @@ describe('Authenticator', () => {
98115
expect(container.instance).toBeNull();
99116
}
100117
);
118+
119+
it('renders with custom slot components as expected', () => {
120+
useAuthenticatorSpy.mockReturnValueOnce({
121+
route: 'signIn',
122+
} as unknown as UseAuthenticator);
123+
124+
const { getByTestId, toJSON } = render(
125+
<Authenticator Container={Container} Footer={Footer} Header={Header} />
126+
);
127+
128+
expect(getByTestId(CONTAINER_TEST_ID)).toBeDefined();
129+
expect(getByTestId(FOOTER_TEST_ID)).toBeDefined();
130+
expect(getByTestId(HEADER_TEST_ID)).toBeDefined();
131+
132+
expect(toJSON()).toMatchSnapshot();
133+
});
101134
});

packages/react-native/src/Authenticator/__tests__/__snapshots__/Authenticator.spec.tsx.snap

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,25 @@ exports[`Authenticator handles the signOut route as expected with children 1`] =
7474
Test Children
7575
</Text>
7676
`;
77+
78+
exports[`Authenticator renders with custom slot components as expected 1`] = `
79+
<SafeAreaProvider>
80+
<View
81+
testID="container-test-id"
82+
>
83+
<View
84+
testID="header-test-id"
85+
/>
86+
<View
87+
style={
88+
Object {
89+
"marginHorizontal": 16,
90+
}
91+
}
92+
/>
93+
<View
94+
testID="footer-test-id"
95+
/>
96+
</View>
97+
</SafeAreaProvider>
98+
`;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as DefaultContainer } from './DefaultContainer';
22
export { default as InnerContainer } from './InnerContainer';
3+
export { DefaultContainerComponent } from './types';

packages/react-native/src/Authenticator/common/DefaultContainer/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface ContainerStyles {
1515
export interface ContainerProps
1616
extends ScrollViewProps,
1717
Pick<KeyboardAvoidingViewProps, 'behavior' | 'keyboardVerticalOffset'> {
18+
children: React.ReactNode;
1819
keyboardAvoidingViewStyle?: StyleProp<ViewStyle>;
1920
scrollViewContentContainerStyle?: StyleProp<ViewStyle>;
2021
}

packages/react-native/src/Authenticator/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22

33
import { AuthenticatorMachineOptions } from '@aws-amplify/ui';
44

5+
import { DefaultContainerComponent } from './common';
56
import { Components } from './Defaults';
67

78
type SupportedAuthenticatorMachineOptions = Omit<
@@ -16,6 +17,9 @@ type SupportedAuthenticatorMachineOptions = Omit<
1617
export interface AuthenticatorProps
1718
extends SupportedAuthenticatorMachineOptions {
1819
children?: React.ReactNode;
20+
Container?: DefaultContainerComponent;
21+
Footer?: React.ComponentType;
22+
Header?: React.ComponentType;
1923
components?: Components;
2024
}
2125

0 commit comments

Comments
 (0)