diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index feafe32e..4e2e7f80 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
- node-version: [16.x]
+ node-version: [18.x]
steps:
- uses: actions/checkout@v1
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 893eddd6..70ffdf77 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,5 @@
{
- "cSpell.words": ["icomoon"],
+ "cSpell.words": ["icomoon", "notificated", "postsecondary"],
"todohighlight.keywords": [
{
"text": "CONFIG:",
diff --git a/App.tsx b/App.tsx
index d7211dba..e22bc10e 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1,12 +1,15 @@
// FIXME: see how why did you render works
// import './wdyr'
+
+// This is added to support web for reaniamted: https://github.com/software-mansion/react-native-reanimated/issues/4140#issuecomment-1455209588
import 'setimmediate'
import 'react-native-reanimated'
+
+// Rest imports
import '~i18n'
import * as Device from 'expo-device'
+import 'expo-router/entry'
-import { Navigation } from '~navigation'
-import { Providers } from '~providers'
import { enableAndroidBackgroundNotificationListener, startMockedServer } from '~services'
// FIXME: there is some issue with miragejs that causes console.log to not work
@@ -27,13 +30,3 @@ if (DISABLE_CONSOLE_ENABLE_MOCKED_SERVER) {
if (Device.isDevice) {
enableAndroidBackgroundNotificationListener()
}
-
-const App = (): JSX.Element => {
- return (
-
-
-
- )
-}
-
-export default App
diff --git a/app.json b/app.json
index ece771cb..660fdbf7 100644
--- a/app.json
+++ b/app.json
@@ -22,6 +22,9 @@
},
"userInterfaceStyle": "automatic",
"version": "2.1.0",
- "plugins": ["expo-font", "expo-localization", "expo-secure-store"]
+ "plugins": ["expo-font", "expo-localization", "expo-secure-store", "expo-router"],
+ "web": {
+ "bundler": "metro"
+ }
}
}
diff --git a/app/(app)/(auth)/_layout.tsx b/app/(app)/(auth)/_layout.tsx
new file mode 100644
index 00000000..df38fb8e
--- /dev/null
+++ b/app/(app)/(auth)/_layout.tsx
@@ -0,0 +1,17 @@
+import { Redirect, Stack } from 'expo-router'
+
+import { useAuth } from '~hooks'
+
+export const unstable_settings = {
+ initialRouteName: 'sign-in',
+}
+
+export default function AuthLayout() {
+ const { isSignedIn } = useAuth()
+
+ if (isSignedIn === true) {
+ return
+ }
+
+ return
+}
diff --git a/app/(app)/(auth)/sign-in.tsx b/app/(app)/(auth)/sign-in.tsx
new file mode 100644
index 00000000..f75d4a25
--- /dev/null
+++ b/app/(app)/(auth)/sign-in.tsx
@@ -0,0 +1,3 @@
+import { SignInScreen } from '~screens'
+
+export default SignInScreen
diff --git a/app/(app)/(auth)/sign-up.tsx b/app/(app)/(auth)/sign-up.tsx
new file mode 100644
index 00000000..94ad1b91
--- /dev/null
+++ b/app/(app)/(auth)/sign-up.tsx
@@ -0,0 +1,3 @@
+import { SignUpScreen } from '~screens'
+
+export default SignUpScreen
diff --git a/app/(app)/(modals)/_layout.tsx b/app/(app)/(modals)/_layout.tsx
new file mode 100644
index 00000000..47022e3b
--- /dev/null
+++ b/app/(app)/(modals)/_layout.tsx
@@ -0,0 +1,9 @@
+import { Stack } from 'expo-router'
+
+export const unstable_settings = {
+ initialRouteName: 'application-info',
+}
+
+export default function ModalsLayout() {
+ return
+}
diff --git a/app/(app)/(modals)/application-info.tsx b/app/(app)/(modals)/application-info.tsx
new file mode 100644
index 00000000..d6ae0616
--- /dev/null
+++ b/app/(app)/(modals)/application-info.tsx
@@ -0,0 +1,3 @@
+import { ApplicationInfoScreen } from '~screens'
+
+export default ApplicationInfoScreen
diff --git a/app/(app)/(tabs)/_layout.tsx b/app/(app)/(tabs)/_layout.tsx
new file mode 100644
index 00000000..4209b19f
--- /dev/null
+++ b/app/(app)/(tabs)/_layout.tsx
@@ -0,0 +1,18 @@
+import { Redirect } from 'expo-router'
+
+import { useAuth } from '~hooks'
+import { ResponsiveNavigator } from '~navigation/tabNavigator/navigator'
+
+export const unstable_settings = {
+ initialRouteName: 'home',
+}
+
+export default function TabLayout() {
+ const { isSignedIn } = useAuth()
+
+ if (isSignedIn === false) {
+ return
+ }
+
+ return
+}
diff --git a/app/(app)/(tabs)/example/_layout.tsx b/app/(app)/(tabs)/example/_layout.tsx
new file mode 100644
index 00000000..92789ee5
--- /dev/null
+++ b/app/(app)/(tabs)/example/_layout.tsx
@@ -0,0 +1,9 @@
+import { Stack } from 'expo-router'
+
+export const unstable_settings = {
+ initialRouteName: 'index',
+}
+
+export default function DynamicLayout() {
+ return
+}
diff --git a/app/(app)/(tabs)/example/colors.tsx b/app/(app)/(tabs)/example/colors.tsx
new file mode 100644
index 00000000..6721eee5
--- /dev/null
+++ b/app/(app)/(tabs)/example/colors.tsx
@@ -0,0 +1,3 @@
+import { ColorsScreen } from '~screens'
+
+export default ColorsScreen
diff --git a/app/(app)/(tabs)/example/components.tsx b/app/(app)/(tabs)/example/components.tsx
new file mode 100644
index 00000000..6f5c79e2
--- /dev/null
+++ b/app/(app)/(tabs)/example/components.tsx
@@ -0,0 +1,3 @@
+import { ComponentsScreen } from '~screens'
+
+export default ComponentsScreen
diff --git a/app/(app)/(tabs)/example/data-from-be.tsx b/app/(app)/(tabs)/example/data-from-be.tsx
new file mode 100644
index 00000000..e7368d3a
--- /dev/null
+++ b/app/(app)/(tabs)/example/data-from-be.tsx
@@ -0,0 +1,3 @@
+import { DataFromBeScreen_EXAMPLE } from '~screens'
+
+export default DataFromBeScreen_EXAMPLE
diff --git a/app/(app)/(tabs)/example/index.tsx b/app/(app)/(tabs)/example/index.tsx
new file mode 100644
index 00000000..5343ca7f
--- /dev/null
+++ b/app/(app)/(tabs)/example/index.tsx
@@ -0,0 +1,3 @@
+import { ExamplesScreen } from '~screens'
+
+export default ExamplesScreen
diff --git a/app/(app)/(tabs)/example/test-form.tsx b/app/(app)/(tabs)/example/test-form.tsx
new file mode 100644
index 00000000..16c87b63
--- /dev/null
+++ b/app/(app)/(tabs)/example/test-form.tsx
@@ -0,0 +1,3 @@
+import { TestFormScreen } from '~screens'
+
+export default TestFormScreen
diff --git a/app/(app)/(tabs)/example/typography.tsx b/app/(app)/(tabs)/example/typography.tsx
new file mode 100644
index 00000000..467b08e6
--- /dev/null
+++ b/app/(app)/(tabs)/example/typography.tsx
@@ -0,0 +1,3 @@
+import { TypographyScreen } from '~screens'
+
+export default TypographyScreen
diff --git a/app/(app)/(tabs)/home/_layout.tsx b/app/(app)/(tabs)/home/_layout.tsx
new file mode 100644
index 00000000..92789ee5
--- /dev/null
+++ b/app/(app)/(tabs)/home/_layout.tsx
@@ -0,0 +1,9 @@
+import { Stack } from 'expo-router'
+
+export const unstable_settings = {
+ initialRouteName: 'index',
+}
+
+export default function DynamicLayout() {
+ return
+}
diff --git a/app/(app)/(tabs)/home/details.tsx b/app/(app)/(tabs)/home/details.tsx
new file mode 100644
index 00000000..45347ddb
--- /dev/null
+++ b/app/(app)/(tabs)/home/details.tsx
@@ -0,0 +1,3 @@
+import { DetailsScreen } from '~screens'
+
+export default DetailsScreen
diff --git a/app/(app)/(tabs)/home/index.tsx b/app/(app)/(tabs)/home/index.tsx
new file mode 100644
index 00000000..dbfe4261
--- /dev/null
+++ b/app/(app)/(tabs)/home/index.tsx
@@ -0,0 +1,3 @@
+import { HomeScreen } from '~screens'
+
+export default HomeScreen
diff --git a/app/(app)/(tabs)/settings/_layout.tsx b/app/(app)/(tabs)/settings/_layout.tsx
new file mode 100644
index 00000000..92789ee5
--- /dev/null
+++ b/app/(app)/(tabs)/settings/_layout.tsx
@@ -0,0 +1,9 @@
+import { Stack } from 'expo-router'
+
+export const unstable_settings = {
+ initialRouteName: 'index',
+}
+
+export default function DynamicLayout() {
+ return
+}
diff --git a/app/(app)/(tabs)/settings/index.tsx b/app/(app)/(tabs)/settings/index.tsx
new file mode 100644
index 00000000..8e6518a0
--- /dev/null
+++ b/app/(app)/(tabs)/settings/index.tsx
@@ -0,0 +1,3 @@
+import { SettingsScreen } from '~screens'
+
+export default SettingsScreen
diff --git a/app/(app)/_layout.tsx b/app/(app)/_layout.tsx
new file mode 100644
index 00000000..c4bc25a2
--- /dev/null
+++ b/app/(app)/_layout.tsx
@@ -0,0 +1,11 @@
+import { Stack } from 'expo-router'
+
+export default function AppLayout() {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/app/+html.tsx b/app/+html.tsx
new file mode 100644
index 00000000..94f3c90c
--- /dev/null
+++ b/app/+html.tsx
@@ -0,0 +1,46 @@
+import { ScrollViewStyleReset } from 'expo-router/html'
+
+// This file is web-only and used to configure the root HTML for every
+// web page during static rendering.
+// The contents of this function only run in Node.js environments and
+// do not have access to the DOM or browser APIs.
+export default function Root({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+ {/*
+ This viewport disables scaling which makes the mobile website act more like a native app.
+ However this does reduce built-in accessibility. If you want to enable scaling, use this instead:
+
+ */}
+
+ {/*
+ Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
+ However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
+ */}
+
+
+ {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
+
+ {/* Add any additional elements that you want globally available on web... */}
+
+ {children}
+
+ )
+}
+
+const responsiveBackground = `
+body {
+ background-color: #fff;
+}
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: #000;
+ }
+}`
diff --git a/app/[...unmatched].tsx b/app/[...unmatched].tsx
new file mode 100644
index 00000000..97b638f5
--- /dev/null
+++ b/app/[...unmatched].tsx
@@ -0,0 +1,3 @@
+import { NotFoundScreen } from '~screens'
+
+export default NotFoundScreen
diff --git a/app/_layout.tsx b/app/_layout.tsx
new file mode 100644
index 00000000..65648c9d
--- /dev/null
+++ b/app/_layout.tsx
@@ -0,0 +1,37 @@
+import { ThemeProvider } from '@react-navigation/native'
+import { Slot } from 'expo-router'
+
+import { AbsoluteFullFill, Loader } from '~components'
+import { useAuth, useNavigationTheme } from '~hooks'
+import { Providers } from '~providers'
+
+export const unstable_settings = {
+ initialRouteName: 'index',
+}
+
+const Layout = () => {
+ const { isSignedIn } = useAuth()
+ const { navigationTheme } = useNavigationTheme()
+
+ if (isSignedIn === null) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ )
+}
+
+export default function RootLayout() {
+ return (
+
+
+
+ )
+}
diff --git a/app/index.tsx b/app/index.tsx
new file mode 100644
index 00000000..dd6b8465
--- /dev/null
+++ b/app/index.tsx
@@ -0,0 +1,13 @@
+import { Redirect } from 'expo-router'
+
+import { useAuth } from '~hooks'
+
+export default function Root() {
+ const { isSignedIn } = useAuth()
+
+ if (isSignedIn === false) {
+ return
+ }
+
+ return
+}
diff --git a/assets/logo/logo-full-dark.png b/assets/logo/logo-full-dark.png
new file mode 100644
index 00000000..a14668ad
Binary files /dev/null and b/assets/logo/logo-full-dark.png differ
diff --git a/assets/logo/logo-full-light.png b/assets/logo/logo-full-light.png
new file mode 100644
index 00000000..5f767a5a
Binary files /dev/null and b/assets/logo/logo-full-light.png differ
diff --git a/assets/logo/logo-sygnet-dark.png b/assets/logo/logo-sygnet-dark.png
new file mode 100644
index 00000000..a64f209b
Binary files /dev/null and b/assets/logo/logo-sygnet-dark.png differ
diff --git a/assets/logo/logo-sygnet-light.png b/assets/logo/logo-sygnet-light.png
new file mode 100644
index 00000000..eaba3433
Binary files /dev/null and b/assets/logo/logo-sygnet-light.png differ
diff --git a/declaration.d.ts b/declaration.d.ts
new file mode 100644
index 00000000..acd2815e
--- /dev/null
+++ b/declaration.d.ts
@@ -0,0 +1,5 @@
+// declaration.d.ts
+declare module '*.scss' {
+ const content: Record
+ export default content
+}
diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts
index 43a51177..95c575b1 100644
--- a/docs/docusaurus.config.ts
+++ b/docs/docusaurus.config.ts
@@ -2,7 +2,7 @@ import type * as Preset from '@docusaurus/preset-classic'
import type { Config } from '@docusaurus/types'
import { themes as prismThemes } from 'prism-react-renderer'
-const repoLink = 'https://github.com/binarapps/expo-ts-template'
+const repoLink = 'https://github.com/binarapps/baca-react-native-template'
const config: Config = {
title: 'BACA - react native starter',
@@ -39,15 +39,7 @@ const config: Config = {
sidebarPath: './sidebars.ts',
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
- editUrl:
- 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
- },
- blog: {
- showReadingTime: true,
- // Please change this to your repo.
- // Remove this to remove the "edit this page" links.
- editUrl:
- 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
+ editUrl: repoLink + '/tree/main/docs/',
},
theme: {
customCss: './src/css/custom.css',
diff --git a/package.json b/package.json
index 0ded92dc..a9cc6573 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"eslint",
"prettier"
],
+ "main": "expo-router/entry",
"repository": {
"type": "git",
"url": "https://github.com/binarapps/baca-react-native-template.git"
@@ -85,6 +86,7 @@
"whoami": "expo whoami"
},
"dependencies": {
+ "@bacons/react-views": "^1.1.3",
"@expo/config-plugins": "~7.8.0",
"@expo/prebuild-config": "~6.7.0",
"@expo/vector-icons": "^14.0.0",
@@ -116,6 +118,7 @@
"expo-localization": "~14.8.3",
"expo-network": "~5.8.0",
"expo-notifications": "~0.27.5",
+ "expo-router": "~3.4.6",
"expo-screen-orientation": "~6.4.1",
"expo-secure-store": "~12.8.1",
"expo-splash-screen": "~0.26.4",
@@ -140,6 +143,7 @@
"react-native-svg": "14.1.0",
"react-native-web": "~0.19.6",
"reactotron-react-native": "^5.0.3",
+ "sass": "^1.70.0",
"setimmediate": "^1.0.5",
"use-debounce": "^9.0.4"
},
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 00000000..5ce3536e
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,48 @@
+import { NativeStackHeaderProps } from '@react-navigation/native-stack'
+import { useRouter } from 'expo-router'
+
+import { Touchable } from './atoms'
+
+import { Box, Column, Row, Icon, Text } from '~components/atoms'
+
+const logoHeight = 24
+
+export const Header = ({ options, ...rest }: NativeStackHeaderProps) => {
+ const router = useRouter()
+
+ console.log('options', {
+ options,
+ rest,
+ canGoBack: router.canGoBack(),
+ restxd: rest.navigation.getState(),
+ canGoBack2: rest.navigation.canGoBack(),
+ })
+
+ return (
+
+
+
+ {router.canGoBack() && (
+
+
+
+ )}
+
+ {options.title ? (
+ <>
+
+
+
+ {options.title}
+
+
+ >
+ ) : (
+
+ )}
+
+
+
+
+ )
+}
diff --git a/src/components/LanguagePicker.tsx b/src/components/LanguagePicker.tsx
index 13c57580..39e65ed8 100644
--- a/src/components/LanguagePicker.tsx
+++ b/src/components/LanguagePicker.tsx
@@ -21,7 +21,7 @@ export const LanguagePicker: React.FC = () => {
const { colorScheme } = useColorScheme()
const { i18n } = useTranslation()
- const language = i18n.language.slice(0, 2).toUpperCase() as keyof typeof languages
+ const language = i18n?.language?.slice?.(0, 2).toUpperCase() as keyof typeof languages
const isOpen = useSharedValue(false)
const rotateZ = useDerivedValue(() => withTiming(isOpen.value ? 180 : 0))
@@ -49,7 +49,7 @@ export const LanguagePicker: React.FC = () => {
- {languages[language].emoji}
+ {languages?.[language]?.emoji}
diff --git a/src/components/README.md b/src/components/README.md
index 96e9044a..815855c1 100644
--- a/src/components/README.md
+++ b/src/components/README.md
@@ -61,9 +61,9 @@ import { Input, AbsoluteFullFill } from '~components'
const MyComponent: React.FC = () => (
@@ -107,18 +107,18 @@ import { Spacer, Input, Container } from '~components'
const MyComponent: React.FC = () => (
@@ -195,12 +195,12 @@ import { Field } from '~components'
const MyComponent: React.FC = () => (
)
@@ -285,16 +285,15 @@ const MyComponent: React.FC = () => {
return (
void }) => {
}, [version])
return (
-
+
{version}
diff --git a/src/components/atoms/Button/__snapshots__/Button.test.tsx.snap b/src/components/atoms/Button/__snapshots__/Button.test.tsx.snap
index 79fca203..7f4b1f9b 100644
--- a/src/components/atoms/Button/__snapshots__/Button.test.tsx.snap
+++ b/src/components/atoms/Button/__snapshots__/Button.test.tsx.snap
@@ -54,7 +54,7 @@ exports[`Button renders correctly 1`] = `
style={
{
"color": "#ffffff",
- "fontFamily": "Lato-Regular",
+ "fontFamily": "Lato_700Bold",
"fontSize": 16,
"fontStyle": "normal",
"fontWeight": "400",
diff --git a/src/components/atoms/Input.tsx b/src/components/atoms/Input.tsx
index a865864e..f574ac73 100644
--- a/src/components/atoms/Input.tsx
+++ b/src/components/atoms/Input.tsx
@@ -1,5 +1,11 @@
import { forwardRef } from 'react'
-import { NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextStyle } from 'react-native'
+import {
+ NativeSyntheticEvent,
+ Platform,
+ TextInput,
+ TextInputFocusEventData,
+ TextStyle,
+} from 'react-native'
import { Box } from './Box'
import { Icon } from './Icon'
@@ -191,11 +197,12 @@ export const Input = forwardRef(
const handleFocus = useCallback(
(e?: NativeSyntheticEvent) => {
+ if (isDisabled) return
_inputRef.current?.focus()
setIsFocused(true)
if (onFocus && e) onFocus(e)
},
- [setIsFocused, onFocus]
+ [isDisabled, onFocus]
)
const handleBlur = useCallback(
@@ -236,7 +243,10 @@ export const Input = forwardRef(
autoCapitalize="none"
color={isInvalid ? 'danger' : 'text'}
cursorColor={colors.primaryLight}
- editable={!isDisabled}
+ {...Platform.select({
+ default: { editable: !isDisabled },
+ web: { disabled: isDisabled },
+ })}
flex={1}
fontFamily="regular"
fontSize="xs"
diff --git a/src/components/atoms/Text/Text.test.tsx b/src/components/atoms/Text/Text.test.tsx
index 249d6f84..40449834 100644
--- a/src/components/atoms/Text/Text.test.tsx
+++ b/src/components/atoms/Text/Text.test.tsx
@@ -5,6 +5,9 @@ import { cleanup, render } from '~utils/testUtils'
afterEach(cleanup)
+console.error = jest.fn()
+console.warn = jest.fn()
+
const defaultTextStyles = {
textTransform: 'none',
color: theme.light.colors.text,
@@ -115,8 +118,8 @@ describe('Text', () => {
)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontWeight: 'bold',
fontStyle: 'italic',
+ fontWeight: 'bold',
color: theme.light.colors.red[400],
textDecorationLine: 'underline',
})
@@ -126,9 +129,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 48,
- fontWeight: 'normal',
})
})
@@ -136,9 +138,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 48,
- fontWeight: 'normal',
})
})
@@ -146,9 +147,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 48,
- fontWeight: 'bold',
})
})
@@ -156,9 +156,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 48,
- fontWeight: 'bold',
})
})
@@ -166,9 +165,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 36,
- fontWeight: 'normal',
})
})
@@ -176,9 +174,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 36,
- fontWeight: 'normal',
})
})
@@ -186,9 +183,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 36,
- fontWeight: 'bold',
})
})
@@ -196,9 +192,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 36,
- fontWeight: 'bold',
})
})
@@ -206,9 +201,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 30,
- fontWeight: 'normal',
})
})
@@ -216,9 +210,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 30,
- fontWeight: 'normal',
})
})
@@ -226,9 +219,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 30,
- fontWeight: 'bold',
})
})
@@ -236,9 +228,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 30,
- fontWeight: 'bold',
})
})
@@ -246,9 +237,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 24,
- fontWeight: 'normal',
})
})
@@ -256,9 +246,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 24,
- fontWeight: 'normal',
})
})
@@ -266,9 +255,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 24,
- fontWeight: 'bold',
})
})
@@ -276,9 +264,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 24,
- fontWeight: 'bold',
})
})
@@ -286,9 +273,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 20,
- fontWeight: 'normal',
})
})
@@ -296,9 +282,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 20,
- fontWeight: 'normal',
})
})
@@ -306,9 +291,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 20,
- fontWeight: 'bold',
})
})
@@ -316,9 +300,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 20,
- fontWeight: 'bold',
})
})
@@ -326,9 +309,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 18,
- fontWeight: 'normal',
})
})
@@ -336,9 +318,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_400Regular',
fontSize: 18,
- fontWeight: 'normal',
})
})
@@ -346,9 +327,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 18,
- fontWeight: 'bold',
})
})
@@ -356,9 +336,8 @@ describe('Text', () => {
const { getByText } = render(Hello World)
expect(getByText('Hello World').props.style).toStrictEqual({
...defaultTextStyles,
- fontFamily: 'Lato-Bold',
+ fontFamily: 'Lato_700Bold',
fontSize: 18,
- fontWeight: 'bold',
})
})
})
diff --git a/src/components/atoms/Text/Text.tsx b/src/components/atoms/Text/Text.tsx
index c88b92ac..a540f81d 100644
--- a/src/components/atoms/Text/Text.tsx
+++ b/src/components/atoms/Text/Text.tsx
@@ -12,7 +12,7 @@ type TypographyProps = {
fontSize?: FontSizes | number
letterSpacing?: LetterSpacings
lineHeight?: LineHeights
- fontWeight?: FontWeights
+ fontWeight?: TextStyle['fontWeight']
fontFamily?: Fonts
color?: ColorNames
noOfLines?: number
@@ -46,6 +46,7 @@ const RawText = memo(
textTransform,
underline,
uppercase,
+ fontWeight,
lowercase,
style,
variant,
@@ -140,7 +141,7 @@ const RawText = memo(
const textStyle = useMemo(
() =>
generateStyleSheet([
- bold && { fontWeight: 'bold' },
+ (fontWeight || bold) && { fontWeight: fontWeight || 'bold' },
italic && { fontStyle: 'italic' },
fontFamilyStyle,
fontSizeStyle,
@@ -153,15 +154,16 @@ const RawText = memo(
style,
]),
[
+ fontWeight,
bold,
italic,
fontFamilyStyle,
fontSizeStyle,
textAlignmentStyle,
- letterSpacingStyle,
- lineHeightStyle,
textColor,
textDecorationStyle,
+ letterSpacingStyle,
+ lineHeightStyle,
textTransformStyle,
style,
]
diff --git a/src/components/molecules/Field/Input.tsx b/src/components/molecules/Field/Input.tsx
index 21c35918..b0e9186e 100644
--- a/src/components/molecules/Field/Input.tsx
+++ b/src/components/molecules/Field/Input.tsx
@@ -34,7 +34,6 @@ const layoutPropsKeys = [
export const Input = forwardRef, FieldInputProps>(
(
{
- isDisabled,
isRequired,
isInvalid,
label,
diff --git a/src/constants/images.ts b/src/constants/images.ts
index e8414bd7..fa14b904 100644
--- a/src/constants/images.ts
+++ b/src/constants/images.ts
@@ -1,3 +1,9 @@
/* eslint-disable @typescript-eslint/no-var-requires */
export const darkLogo = require('~assets/logo-dark.png')
export const lightLogo = require('~assets/logo-light.png')
+
+export const darkLogoFull = require('~assets/logo/logo-full-dark.png')
+export const lightLogoFull = require('~assets/logo/logo-full-light.png')
+
+export const darkLogoSygnet = require('~assets/logo/logo-sygnet-dark.png')
+export const lightLogoSygnet = require('~assets/logo/logo-sygnet-light.png')
diff --git a/src/hooks/navigation/index.ts b/src/hooks/navigation/index.ts
index 52c79acc..3dca4660 100644
--- a/src/hooks/navigation/index.ts
+++ b/src/hooks/navigation/index.ts
@@ -16,5 +16,6 @@ export { useCardAnimation, useGestureHandlerRef } from '@react-navigation/stack'
export * from './useNavigationStatePersistence'
export * from './useNavigationTheme'
export * from './usePreventGoBack'
+export * from './useScreenOptions'
export * from './useScreenTracker'
export * from './useWeb'
diff --git a/src/hooks/navigation/useNavigationStatePersistence.ts b/src/hooks/navigation/useNavigationStatePersistence.ts
index 1f735f5b..88cf54ac 100644
--- a/src/hooks/navigation/useNavigationStatePersistence.ts
+++ b/src/hooks/navigation/useNavigationStatePersistence.ts
@@ -20,6 +20,7 @@ type NavigationStatePersistenceReturn = {
const { NAVIGATION_STATE } = ASYNC_STORAGE_KEYS
+// TODO: make this work on expo router
export const useNavigationStatePersistence = (): NavigationStatePersistenceReturn => {
const [isReady, setIsReady] = useState(isProduction)
const [initialState, setInitialState] = useState()
diff --git a/src/hooks/navigation/useNavigationTheme.ts b/src/hooks/navigation/useNavigationTheme.ts
index 2ef7fc5f..a2eb1c41 100644
--- a/src/hooks/navigation/useNavigationTheme.ts
+++ b/src/hooks/navigation/useNavigationTheme.ts
@@ -17,6 +17,9 @@ export const useNavigationTheme = () => {
backgroundColor: colorScheme === 'dark' ? colors.gray[900] : colors.light,
paddingTop: 4,
},
+ tabBarIconStyle: {
+ marginTop: 0,
+ },
}),
[colors.primary, colors.gray, colors.light, colorScheme]
)
diff --git a/src/hooks/navigation/usePreventGoBack.ts b/src/hooks/navigation/usePreventGoBack.ts
index ab2012e9..98494015 100644
--- a/src/hooks/navigation/usePreventGoBack.ts
+++ b/src/hooks/navigation/usePreventGoBack.ts
@@ -1,42 +1,66 @@
-import { useNavigation } from '@react-navigation/native'
-import { useEffect } from 'react'
+import {
+ EventListenerCallback,
+ EventMapCore,
+ usePreventRemoveContext,
+ useRoute,
+} from '@react-navigation/native'
+import { useNavigation } from 'expo-router'
+import { nanoid } from 'nanoid/non-secure'
+import React from 'react'
import { useTranslation } from 'react-i18next'
+import useLatestCallback from 'use-latest-callback'
import { alert } from '~utils'
-export const usePreventGoBack = (shouldPrevent = true): void => {
+// This is copy of this hook: usePreventRemove
+// https://github.com/react-navigation/react-navigation/blob/90cfbf23bcc5259f3262691a9eec6c5b906e5262/packages/core/src/usePreventRemove.tsx#L17
+// Previous implementation wasn't working properly on expo router
+export const usePreventGoBack = (preventRemove = true) => {
const { t } = useTranslation()
+ const [id] = React.useState(() => nanoid())
+
const navigation = useNavigation()
+ const { key: routeKey } = useRoute()
+
+ const { setPreventRemove } = usePreventRemoveContext()
- useEffect(() => {
+ React.useEffect(() => {
+ setPreventRemove(id, routeKey, preventRemove)
+ return () => {
+ setPreventRemove(id, routeKey, false)
+ }
+ }, [setPreventRemove, id, routeKey, preventRemove])
+
+ const beforeRemoveListener = useLatestCallback<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- const callback = (event: any) => {
- if (!shouldPrevent) {
- return
- }
-
- event?.preventDefault()
-
- alert(
- t('navigation.prevent_go_back_alert.title'),
- t('navigation.prevent_go_back_alert.description'),
- [
- {
- text: t('navigation.prevent_go_back_alert.dont_leave'),
- style: 'cancel',
- onPress: () => undefined,
- },
- {
- text: t('navigation.prevent_go_back_alert.discard'),
- style: 'destructive',
- onPress: () => navigation.dispatch(event?.data?.action),
- },
- ]
- )
+ EventListenerCallback, 'beforeRemove'>
+ >((e) => {
+ if (!preventRemove) {
+ return
}
- navigation.addListener('beforeRemove', callback)
+ e.preventDefault()
+
+ alert(
+ t('navigation.prevent_go_back_alert.title'),
+ t('navigation.prevent_go_back_alert.description'),
+ [
+ {
+ text: t('navigation.prevent_go_back_alert.do_not_leave'),
+ style: 'cancel',
+ onPress: () => undefined,
+ },
+ {
+ text: t('navigation.prevent_go_back_alert.discard'),
+ style: 'destructive',
+ onPress: () => navigation.dispatch(e?.data?.action),
+ },
+ ]
+ )
+ })
- return () => navigation.removeListener('beforeRemove', callback)
- }, [navigation, shouldPrevent, t])
+ React.useEffect(
+ () => navigation?.addListener('beforeRemove', beforeRemoveListener),
+ [navigation, beforeRemoveListener]
+ )
}
diff --git a/src/hooks/navigation/useScreenOptions.ts b/src/hooks/navigation/useScreenOptions.ts
new file mode 100644
index 00000000..82374a63
--- /dev/null
+++ b/src/hooks/navigation/useScreenOptions.ts
@@ -0,0 +1,16 @@
+import { useNavigation } from 'expo-router'
+import { useLayoutEffect } from 'react'
+
+export const useScreenOptions = ({
+ title,
+ presentation,
+}: {
+ title?: string
+ presentation?: string
+}) => {
+ const navigation = useNavigation()
+
+ useLayoutEffect(() => {
+ navigation.setOptions({ title, presentation })
+ }, [navigation, presentation, title])
+}
diff --git a/src/hooks/navigation/useScreenTracker.ts b/src/hooks/navigation/useScreenTracker.ts
index d2761201..a88695fe 100644
--- a/src/hooks/navigation/useScreenTracker.ts
+++ b/src/hooks/navigation/useScreenTracker.ts
@@ -16,6 +16,7 @@ type ScreenTrackerReturn = {
onStateChange: () => Promise
}
+// TODO: make this work on expo router
export const useScreenTracker = (callback = defaultCallback): ScreenTrackerReturn => {
const routeNameRef = useRef()
diff --git a/src/hooks/navigation/useWeb.ts b/src/hooks/navigation/useWeb.ts
index bebff2a5..44f85d1e 100644
--- a/src/hooks/navigation/useWeb.ts
+++ b/src/hooks/navigation/useWeb.ts
@@ -13,8 +13,6 @@ const { desktop, tablet } = breakpoints
export const useWeb: () => ReturnType = () => {
const [width, setWidth] = useState(0)
- console.log('useWeb', width)
-
useEffect(() => {
const setDimensions = (windowWidth: number, screenWidth: number) => {
switch (true) {
diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json
index b0d3d8e1..5024e7bd 100644
--- a/src/i18n/translations/en.json
+++ b/src/i18n/translations/en.json
@@ -1,7 +1,31 @@
{
+ "common": {
+ "add": "Add",
+ "back": "Back",
+ "cancel": "Cancel",
+ "email_label": "E-mail",
+ "email_placeholder": "john@doe.com",
+ "empty_list": "The list is empty",
+ "go_back": "Go back",
+ "log_in": "Login",
+ "phone_label": "Telephone",
+ "remove": "Remove",
+ "save": "Save",
+ "search": "Search",
+ "send_again": "Send again",
+ "show_on_map": "Show on map",
+ "sign_in": "Sign in",
+ "sign_up": "Register",
+ "try_again_later": "Please try again later",
+ "try_again": "Try again",
+ "user_label": "User",
+ "user_placeholder": "user"
+ },
"errors": {
"screen_not_found": "NotFound screen",
- "something_went_wrong": "Something went wrong"
+ "something_went_wrong": "Something went wrong",
+ "token_expired": "Token expired",
+ "missing_auth": "Missing auth"
},
"form": {
"invalid_email_format": "Incorrect e-mail address",
@@ -11,77 +35,41 @@
"prevent_go_back_alert": {
"title": "Discard changes?",
"description": "You have unsaved changes. Are you sure to discard them and leave the screen?",
- "dont_leave": "Don't leave",
+ "do_not_leave": "Don't leave",
"discard": "Discard"
},
"screen_titles": {
- "home_stack": "Home",
+ "application_info": "ApplicationInfo",
+ "colors": "Colors",
+ "components": "Components",
+ "data_from_be_screen_example": "Data from BE",
+ "details": "Details",
"examples_stack": "Examples",
"examples": "Examples",
- "components": "Components",
- "colors": "Colors",
- "typography": "Typography",
+ "home_stack": "Home",
"home": "Home",
- "details": "Details",
- "sign_in": "Sign in",
- "sign_up": "Sign up",
"main_tab": "MainTab",
+ "not_found": "Not Found",
"settings": "Settings",
- "application_info": "ApplicationInfo",
- "not_found": "NotFound",
- "data_from_be_screen_example": "Data from BE"
+ "sign_in": "Sign in",
+ "sign_up": "Sign up",
+ "typography": "Typography"
}
},
- "application_info_screen": {
- "navigation_info": "When you will try to go back it will double ask if you really want to leave \n"
+ "update": {
+ "alert_title": "New version available",
+ "alert_message": "You will have to restart the application to have better experiences while using it",
+ "restart": "Restart"
},
- "sign_in_screen": {
- "sign_in": "Sign in",
- "sign_up": "Sign up",
- "dont_have_an_account": "Don't have an account?",
- "forgot_password": "Forgot your password?",
- "or_pass": "or enter:",
- "email_helper_text": "Email must be here",
- "password_label": "Password",
- "password_helper_text": "Password must be here",
- "password_placeholder": "password",
- "remember_me": "Remember me"
- },
- "sign_up_screen": {
- "agree_terms_label": "I agree to the Terms and Conditions",
- "newsletter_label": "Subscribe to our newsletter",
- "sign_up": "Sign up"
+ "examples_component": {
+ "example": "Example"
},
- "home_screen": {
- "details": "Details"
+ "application_info_screen": {
+ "navigation_info": "When you will try to go back it will double ask if you really want to leave \n"
},
"colors_screen": {
"colors_label": "Colors"
},
- "typography_screen": {
- "text_font_size": "Text.fontSize: ",
- "heading_size": "Heading.size: "
- },
- "details_screen": {
- "title": "This is details screen",
- "open_bottom_sheet": "Open BottomSheetModal",
- "screen_params": "Screen params: {{params}}",
- "awesome": "Awesome 🎉"
- },
- "examples_component": {
- "example": "Example"
- },
- "examples_screen": {
- "header": "This is Example screen",
- "go_to_application_info": "Go to ApplicationInfo",
- "go_to_colors": "Go to Colors",
- "go_to_components": "Go to Components",
- "go_to_typography": "Go to Typography",
- "go_to_home_stack_details": "Go to HomeStackDetails",
- "go_to_settings": "Go to Settings",
- "go_to_screen_with_BEdata": "Go to screen with data from BE",
- "go_to_screen_test_form": "Go to test form"
- },
"components_screen": {
"test_notification": "Test notification",
"typography": {
@@ -96,101 +84,120 @@
"4xl": "4xl"
},
"button_variants": {
- "header": "Button variants",
- "primary": "Button primary",
- "secondary": "Button secondary",
- "outline": "Button outline",
+ "disabled": "Button disabled",
"ghost": "Button ghost",
+ "header": "Button variants",
"link": "Button link",
- "disabled": "Button disabled",
"loading": "Button loading",
+ "outline": "Button outline",
+ "primary": "Button primary",
+ "secondary": "Button secondary",
"with_icons": "Button with icons"
},
"loader_variants": {
- "header": "Loader variants",
- "circle": "Circle loader",
- "bubbles": "Bubbles loader",
"bricks": "Bricks loader",
+ "bubbles": "Bubbles loader",
+ "circle": "Circle loader",
+ "default": "Default loader",
"disk": "Disk loader",
- "default": "Default loader"
+ "header": "Loader variants"
},
"notification": {
- "title": "In-app notification example",
- "description": "by react-native-notificated 🎉"
+ "description": "by react-native-notificated 🎉",
+ "title": "In-app notification example"
}
},
+ "details_screen": {
+ "awesome": "Awesome 🎉",
+ "open_bottom_sheet": "Open BottomSheetModal",
+ "screen_params": "Screen params: {{params}}",
+ "title": "This is details screen"
+ },
+ "examples_screen": {
+ "go_to_application_info": "Go to ApplicationInfo",
+ "go_to_colors": "Go to Colors",
+ "go_to_components": "Go to Components",
+ "go_to_home_stack_details": "Go to HomeStackDetails",
+ "go_to_screen_test_form": "Go to test form",
+ "go_to_screen_with_BEdata": "Go to screen with data from BE",
+ "go_to_settings": "Go to Settings",
+ "go_to_typography": "Go to Typography",
+ "header": "This is Example screen"
+ },
+ "home_screen": {
+ "details": "Details"
+ },
"settings_screen": {
"copy_push_token": "Copy push token",
"current_theme": "Current theme: {{theme}}",
"sign_out": "Sign out!",
"selected": " - selected"
},
+ "sign_in_screen": {
+ "do_not_have_an_account": "Don't have an account?",
+ "email_helper_text": "Email must be here",
+ "forgot_password": "Forgot your password?",
+ "or_pass": "or enter:",
+ "password_helper_text": "Password must be here",
+ "password_label": "Password",
+ "password_placeholder": "password",
+ "remember_me": "Remember me",
+ "sign_in": "Sign in",
+ "sign_up": "Sign up"
+ },
+ "sign_up_screen": {
+ "agree_terms_label": "I agree to the Terms and Conditions",
+ "newsletter_label": "Subscribe to our newsletter",
+ "sign_up": "Sign up"
+ },
"test_form": {
- "interests": "Interests",
+ "additional_comment": "Additional comment",
+ "age": "Age",
+ "city_placeholder": "City",
+ "classic_music": "Classic music",
+ "comment": "Comment",
+ "contact_data": "Contact data",
"cooking": "Cooking",
- "sport": "Sport",
- "games": "Games",
"dancing": "Dancing",
- "age": "Age",
"education": "Education",
- "primary": "Primary",
+ "email_placeholder": "E-mail",
+ "errors": {
+ "age": "Age is required",
+ "city": "City is required",
+ "education": "Education is required",
+ "email": "Email is required",
+ "interests": "At least 1 interest is needed",
+ "music": "At least 1 music is needed",
+ "name": "Name is required",
+ "phone_format": "Phone number must be in form 000-000-000",
+ "phone": "Phone is required",
+ "postalCode_format": "Postal code must be in form 00-000",
+ "postalCode": "Postal code is required",
+ "sex": "Sex is is required",
+ "shoeSize": "Shoe size is required",
+ "surname": "Surname is required"
+ },
+ "female": "Female",
+ "games": "Games",
+ "interests": "Interests",
+ "male": "Male",
"middle": "Middle",
- "secondary": "Secondary",
- "postsecondary": "Postsecondary",
- "contact_data": "Contact data",
"name_placeholder": "Name",
- "surname_placeholder": "Surname",
- "email_placeholder": "E-mail",
"phone_placeholder": "Phone number",
"postalCode_placeholder": "Postal code",
- "city_placeholder": "City",
+ "postsecondary": "Postsecondary",
+ "primary": "Primary",
+ "secondary": "Secondary",
"sex": "Sex",
- "male": "Male",
- "female": "Female",
"shoe_size": "Shoe size",
- "which_music": "what kind of music do you listen ?",
- "classic_music": "Classic music",
- "additiona_comment": "Additional comment",
- "comment": "Comment",
+ "sport": "Sport",
"submit": "Submit",
- "errors": {
- "name": "Name is required",
- "surname": "Surname is required",
- "email": "Email is required",
- "city": "City is required",
- "postalCode": "Postal code is required",
- "postalCode_format": "Postal code must be in form 00-000",
- "phone": "Phone is required",
- "phone_format": "Phone number must be in form 000-000-000",
- "age": "Age is required",
- "sex": "Sex is is required",
- "education": "Education is required",
- "shoeSize": "Shoe size is required",
- "music": "Atleast 1 music is needed",
- "interests": "Atleast 1 interest is needed"
- }
+ "surname_placeholder": "Surname",
+ "which_music": "what kind of music do you listen ?"
},
- "common": {
- "add": "Add",
- "back": "Back",
- "cancel": "Cancel",
- "email_label": "E-mail",
- "email_placeholder": "john@doe.com",
- "empty_list": "The list is empty",
- "go_back": "Go back",
- "log_in": "Login",
- "phone_label": "Telephone",
- "remove": "Remove",
- "save": "Save",
- "search": "Search",
- "send_again": "Send again",
- "show_on_map": "Show on map",
- "sign_in": "Sign in",
- "sign_up": "Register",
- "try_again_later": "Please try again later",
- "try_again": "Try again",
- "user_label": "User",
- "user_placeholder": "user"
+ "typography_screen": {
+ "heading_size": "Heading.size: ",
+ "text_font_size": "Text.fontSize: "
},
"hello": "Welcome",
"thanks": "Thank you for using the best template for expo apps",
diff --git a/src/i18n/translations/pl.json b/src/i18n/translations/pl.json
index 05431078..f03692f9 100644
--- a/src/i18n/translations/pl.json
+++ b/src/i18n/translations/pl.json
@@ -1,7 +1,31 @@
{
+ "common": {
+ "add": "Dodaj",
+ "back": "Cofnij",
+ "cancel": "Anuluj",
+ "email_label": "E-mail",
+ "email_placeholder": "john@doe.com",
+ "empty_list": "Lista jest pusta",
+ "go_back": "Cofnij",
+ "log_in": "Login",
+ "phone_label": "Telefon",
+ "remove": "Usuń",
+ "save": "Zapisz",
+ "search": "Szukaj",
+ "send_again": "Wyślij ponownie",
+ "show_on_map": "Pokaż na mapie",
+ "sign_in": "Zaloguj",
+ "sign_up": "Zarejestruj",
+ "try_again_later": "Proszę spróbuj ponownie później",
+ "try_again": "Spróbuj ponownie",
+ "user_label": "Użytkownik",
+ "user_placeholder": "Użytkownik"
+ },
"errors": {
+ "missing_auth": "Brak autoryzacji",
"screen_not_found": "NotFound screen",
- "something_went_wrong": "Coś poszło nie tak"
+ "something_went_wrong": "Coś poszło nie tak",
+ "token_expired": "Token wygasł"
},
"form": {
"invalid_email_format": "Zły adres e-mail",
@@ -9,79 +33,43 @@
},
"navigation": {
"prevent_go_back_alert": {
- "title": "Anulować zmiany?",
"description": "Masz niezapisane zmiany. Jesteś pewny, że chcesz je porzucić i wyjść?",
- "dont_leave": "Nie wychodź",
- "discard": "Porzuć"
+ "discard": "Porzuć",
+ "do_not_leave": "Nie wychodź",
+ "title": "Anulować zmiany?"
},
"screen_titles": {
- "home_stack": "Home",
+ "application_info": "ApplicationInfo",
+ "colors": "Colors",
+ "components": "Components",
+ "data_from_be_screen_example": "Dane z backend-u",
+ "details": "Details",
"examples_stack": "Examples",
"examples": "Examples",
- "components": "Components",
- "colors": "Colors",
- "typography": "Typography",
+ "home_stack": "Home",
"home": "Home",
- "details": "Details",
- "sign_in": "Sign in",
- "sign_up": "Sign up",
"main_tab": "MainTab",
- "settings": "Settings",
- "application_info": "ApplicationInfo",
"not_found": "NotFound",
- "data_from_be_screen_example": "Dane z backendu"
+ "settings": "Settings",
+ "sign_in": "Sign in",
+ "sign_up": "Sign up",
+ "typography": "Typography"
}
},
- "application_info_screen": {
- "navigation_info": "Kiedy będziesz próbował cofnąć to zostaniesz podwójnie zapytany, czy na pewno tego chcesz \n"
- },
- "sign_in_screen": {
- "sign_in": "Zaloguj",
- "sign_up": "Zarejestruj",
- "dont_have_an_account": "Nie masz konta?",
- "forgot_password": "Zapomniałeś hasła?",
- "or_pass": "lub wprowadź:",
- "email_helper_text": "Email",
- "password_label": "Hasło",
- "password_helper_text": "Hasła muszą być takie same",
- "password_placeholder": "hasło",
- "remember_me": "Zapamiętaj mnie"
+ "update": {
+ "alert_message": "Będziesz musiał zrestartować aplikację, żeby mieć lepsze doświadczenia podczas jej używania",
+ "alert_title": "Aktualizacja",
+ "restart": "Zrestartuj"
},
- "sign_up_screen": {
- "agree_terms_label": "Zgadzam się z warunkami użytkowania",
- "newsletter_label": "Subskrybuj nasz newsletter",
- "sign_up": "Zarejestruj"
+ "examples_component": {
+ "example": "Przykład"
},
- "home_screen": {
- "details": "Detale"
+ "application_info_screen": {
+ "navigation_info": "Kiedy będziesz próbował cofnąć to zostaniesz podwójnie zapytany, czy na pewno tego chcesz \n"
},
"colors_screen": {
"colors_label": "Kolory"
},
- "typography_screen": {
- "text_font_size": "Text.fontSize: ",
- "heading_size": "Heading.size: "
- },
- "details_screen": {
- "title": "To jest przykładowy widok",
- "open_bottom_sheet": "Otwórz BottomSheetModal",
- "screen_params": "Parametry widoku: {{params}}",
- "awesome": "Wspaniale 🎉"
- },
- "examples_component": {
- "example": "Przyklad"
- },
- "examples_screen": {
- "header": "To jest przykładowy widok",
- "go_to_application_info": "Idź do ApplicationInfo",
- "go_to_colors": "Idź do Kolorów",
- "go_to_components": "Idź do Komponentów",
- "go_to_typography": "Idź do Typografii",
- "go_to_home_stack_details": "Idź do HomeStackDetails",
- "go_to_settings": "Idź do Ustawień",
- "go_to_screen_with_BEdata": "Idź do widoku z danymi z backendu",
- "go_to_screen_test_form": "Idź do formularza testowego"
- },
"components_screen": {
"test_notification": "Test notification",
"typography": {
@@ -96,100 +84,119 @@
"4xl": "4xl"
},
"button_variants": {
- "header": "Button variants",
- "primary": "Button primary",
- "secondary": "Button secondary",
- "outline": "Button outline",
+ "disabled": "Button disabled",
"ghost": "Button ghost",
+ "header": "Button variants",
"link": "Button link",
- "disabled": "Button disabled",
"loading": "Button loading",
+ "outline": "Button outline",
+ "primary": "Button primary",
+ "secondary": "Button secondary",
"with_icons": "Button with icons"
},
"loader_variants": {
- "header": "Loader variants",
- "circle": "Circle loader",
- "bubbles": "Bubbles loader",
"bricks": "Bricks loader",
+ "bubbles": "Bubbles loader",
+ "circle": "Circle loader",
+ "default": "Default loader",
"disk": "Disk loader",
- "default": "Default loader"
+ "header": "Loader variants"
},
"notification": {
- "title": "In-app notification example",
- "description": "by react-native-notificated 🎉"
+ "description": "by react-native-notificated 🎉",
+ "title": "In-app notification example"
}
},
+ "details_screen": {
+ "title": "To jest przykładowy widok",
+ "open_bottom_sheet": "Otwórz BottomSheetModal",
+ "screen_params": "Parametry widoku: {{params}}",
+ "awesome": "Wspaniale 🎉"
+ },
+ "examples_screen": {
+ "header": "To jest przykładowy widok",
+ "go_to_application_info": "Idź do ApplicationInfo",
+ "go_to_colors": "Idź do Kolorów",
+ "go_to_components": "Idź do Komponentów",
+ "go_to_typography": "Idź do Typografii",
+ "go_to_home_stack_details": "Idź do HomeStackDetails",
+ "go_to_settings": "Idź do Ustawień",
+ "go_to_screen_with_BEdata": "Idź do widoku z danymi z backend-u",
+ "go_to_screen_test_form": "Idź do formularza testowego"
+ },
+ "home_screen": {
+ "details": "Detale"
+ },
"settings_screen": {
"current_theme": "Current theme: {{theme}}",
- "sign_out": "Wyloguj!",
- "selected": " - wybrano"
+ "selected": " - wybrano",
+ "sign_out": "Wyloguj!"
+ },
+ "sign_in_screen": {
+ "do_not_have_an_account": "Nie masz konta?",
+ "email_helper_text": "Email",
+ "forgot_password": "Zapomniałeś hasła?",
+ "or_pass": "lub wprowadź:",
+ "password_helper_text": "Hasła muszą być takie same",
+ "password_label": "Hasło",
+ "password_placeholder": "hasło",
+ "remember_me": "Zapamiętaj mnie",
+ "sign_in": "Zaloguj",
+ "sign_up": "Zarejestruj"
+ },
+ "sign_up_screen": {
+ "agree_terms_label": "Zgadzam się z warunkami użytkowania",
+ "newsletter_label": "Subskrybuj nasz newsletter",
+ "sign_up": "Zarejestruj"
},
"test_form": {
- "interests": "Zainteresowania",
+ "additional_comment": "Dodatkowy komentarz",
+ "age": "Wiek",
+ "city_placeholder": "Miasto",
+ "classic_music": "Muzyka klasyczna",
+ "comment": "Komentarz",
+ "contact_data": "Dane kontaktowe",
"cooking": "Gotowanie",
- "sport": "Sport",
- "games": "Gry",
"dancing": "Taniec",
- "age": "Wiek",
"education": "Wykształcenie",
- "primary": "Podstawowe",
+ "email_placeholder": "E-mail",
+ "errors": {
+ "age": "Wiek jest wymagany",
+ "city": "Miasto jest wymagane",
+ "education": "Wykształcenie jest wymagane",
+ "email": "Email jest wymagany",
+ "interests": "Zaznacz przynajmniej 1 zainteresowanie",
+ "music": "Zaznacz przynajmniej 1 rodzaj muzyki",
+ "name": "Imie jest wymagane",
+ "phone_format": "Numer telefonu musi byc w formacie 000-000-000",
+ "phone": "Number telefonu jest wymagany",
+ "postalCode_format": "Kod pocztowy musi byc w formacie 00-000",
+ "postalCode": "Kod pocztowy jest wymagany",
+ "sex": "Płeć jest wymagana",
+ "shoeSize": "Rozmiar buta jest wymagany",
+ "surname": "Nazwisko jest wymagane"
+ },
+ "female": "Kobieta",
+ "games": "Gry",
+ "interests": "Zainteresowania",
+ "male": "Mezczyzna",
"middle": "Gimnazjalne",
- "secondary": "Zawodowe",
- "postsecondary": "Wyzsze",
- "contact_data": "Dane kontaktowe",
"name_placeholder": "Imie",
- "surname_placeholder": "Nazwisko",
- "email_placeholder": "E-mail",
"phone_placeholder": "Telefon",
"postalCode_placeholder": "Kod pocztowy",
- "city_placeholder": "Miasto",
+ "postsecondary": "Wyższe",
+ "primary": "Podstawowe",
+ "secondary": "Zawodowe",
"sex": "Płeć",
- "male": "Mezczyzna",
- "female": "Kobieta",
"shoe_size": "Rozmiar buta",
- "which_music": "Jakiej muzyki sluchasz ?",
- "classic_music": "Muzyka klasyczna",
- "additiona_comment": "Dodatkowy komentarz",
- "comment": "Komentarz",
+ "sport": "Sport",
"submit": "Zatwierdź",
- "errors": {
- "name": "Imie jest wymagane",
- "surname": "Nazwisko jest wymagane",
- "email": "Email jest wymagany",
- "city": "Miasto jest wymagane",
- "postalCode": "Kod pocztowy jest wymagany",
- "postalCode_format": "Kod pocztowy musi byc w formacie 00-000",
- "phone": "Number telefonu jest wymagany",
- "phone_format": "Numer telefonu musi byc w formacie 000-000-000",
- "age": "Wiek jest wymagany",
- "sex": "Płeć jest wymagana",
- "education": "Wykształcenie jest wymagane",
- "shoeSize": "Rozmiar buta jest wymagany",
- "music": "Zaznacz przynajmniej 1 rodzaj muzyki",
- "interests": "Zaznacz przynajmniej 1 zainteresowanie"
- }
+ "surname_placeholder": "Nazwisko",
+ "which_music": "Jakiej muzyki słuchasz ?"
},
- "common": {
- "add": "Dodaj",
- "back": "Cofnij",
- "cancel": "Anuluj",
- "email_label": "E-mail",
- "email_placeholder": "john@doe.com",
- "empty_list": "Lista jest pusta",
- "go_back": "Cofnij",
- "log_in": "Login",
- "phone_label": "Telefon",
- "remove": "Usuń",
- "save": "Zapisz",
- "search": "Szukaj",
- "send_again": "Wyślij ponownie",
- "show_on_map": "Pokaż na mapie",
- "sign_in": "Zaloguj",
- "sign_up": "Zarejestruj",
- "try_again_later": "Proszę spróbuj ponownie później",
- "try_again": "Spróbuj ponownie",
- "user_label": "Użytkownik",
- "user_placeholder": "Użytkownik"
+ "typography_screen": {
+ "text_font_size": "Text.fontSize: ",
+ "heading_size": "Heading.size: "
},
"hello": "Witaj",
"thanks": "Dzięki za używanie najlepszej templatki dla aplikacji expo",
diff --git a/src/navigation/tabNavigator/components/AppHeader.tsx b/src/navigation/tabNavigator/components/AppHeader.tsx
new file mode 100644
index 00000000..5f3eef5e
--- /dev/null
+++ b/src/navigation/tabNavigator/components/AppHeader.tsx
@@ -0,0 +1,44 @@
+import { Image, Platform, StyleSheet, View } from 'react-native'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
+
+import { TabColorsStrings } from '../config'
+
+import { darkLogoFull, lightLogoFull } from '~constants'
+import { useColorScheme } from '~contexts'
+
+export function AppHeader() {
+ const { colorScheme } = useColorScheme()
+ const { top } = useSafeAreaInsets()
+
+ const height = 60 + top
+ return (
+
+
+
+ )
+}
+
+const jsStyles = StyleSheet.create({
+ appHeader: {
+ alignItems: 'center',
+ borderBottomColor: TabColorsStrings.lightGray,
+ borderBottomWidth: 1,
+ flexDirection: 'row',
+ justifyContent: 'center',
+ paddingHorizontal: 16,
+ width: '100%',
+ zIndex: 10,
+ },
+ logoWide: { height: 60, width: 150 },
+})
diff --git a/src/navigation/tabNavigator/components/BottomBar.tsx b/src/navigation/tabNavigator/components/BottomBar.tsx
new file mode 100644
index 00000000..206bc3bb
--- /dev/null
+++ b/src/navigation/tabNavigator/components/BottomBar.tsx
@@ -0,0 +1,73 @@
+import { Platform, StyleSheet, View } from 'react-native'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
+
+import { TabBarItemWrapper } from './TabBarItemWrapper'
+import { bottomTabs, TabColors, TabColorsStrings } from '../config'
+import { cns } from '../utils'
+
+import { Icon } from '~components'
+import { useColorScheme } from '~contexts'
+import cssStyles from '~styles'
+
+export function BottomBar({ visible }: { visible: boolean }) {
+ const { colorScheme } = useColorScheme()
+ return (
+
+
+ {bottomTabs.map((tab, i) => (
+
+ {({ focused, pressed, hovered }) => (
+
+ )}
+
+ ))}
+
+
+ )
+}
+
+const jsStyles = StyleSheet.create({
+ nav: {
+ alignItems: 'center',
+ borderTopColor: TabColorsStrings.lightGray,
+ borderTopWidth: 1,
+ flexDirection: 'row',
+ height: 49,
+ justifyContent: 'space-around',
+ paddingHorizontal: 16,
+ },
+
+ tabIcon: {
+ paddingHorizontal: 8,
+ },
+ tabIconPressed: {
+ opacity: 0.8,
+ transform: [{ scale: 0.9 }],
+ },
+})
diff --git a/src/navigation/tabNavigator/components/HeaderLogo.tsx b/src/navigation/tabNavigator/components/HeaderLogo.tsx
new file mode 100644
index 00000000..c7e6d338
--- /dev/null
+++ b/src/navigation/tabNavigator/components/HeaderLogo.tsx
@@ -0,0 +1,93 @@
+import { Pressable, Text } from '@bacons/react-views'
+import { Link } from 'expo-router'
+import { Image, Platform, StyleSheet, View } from 'react-native'
+
+import { TabColorsStrings } from '../config'
+import { useWidth } from '../hooks'
+import { cns } from '../utils'
+
+import { darkLogoFull, darkLogoSygnet, lightLogoFull, lightLogoSygnet } from '~constants'
+import { useColorScheme } from '~contexts'
+import cssStyles from '~styles'
+
+export function HeaderLogo() {
+ const { colorScheme } = useColorScheme()
+ const isLargeHorizontal = useWidth(1264)
+
+ return (
+
+
+
+ {({ hovered }) => (
+
+
+
+
+ )}
+
+
+
+ )
+}
+
+const jsStyles = StyleSheet.create({
+ headerContainer: {
+ height: 96,
+ minHeight: 96,
+ paddingTop: 0,
+ },
+ headerLink: {
+ alignItems: 'center',
+ },
+ headerLogo: {
+ alignItems: 'center',
+ borderRadius: 8,
+ display: 'flex',
+ margin: 0,
+ },
+ logoSygnet: { height: 60, width: 40 },
+ logoWide: { height: 60, width: 150 },
+})
diff --git a/src/navigation/tabNavigator/components/SideBar.tsx b/src/navigation/tabNavigator/components/SideBar.tsx
new file mode 100644
index 00000000..8c480e91
--- /dev/null
+++ b/src/navigation/tabNavigator/components/SideBar.tsx
@@ -0,0 +1,121 @@
+import { Platform, StyleSheet, View } from 'react-native'
+
+import { HeaderLogo } from './HeaderLogo'
+import { SideBarTabItem } from './SideBarTabItem'
+import { TabColorsStrings, upperSideTabs } from '../config'
+import { useWidth } from '../hooks'
+import { cns } from '../utils'
+
+import { useAuth } from '~hooks'
+import cssStyles from '~styles'
+
+const NAV_MEDIUM_WIDTH = 244
+
+export function SideBar({ visible }: { visible: boolean }) {
+ const isLarge = useWidth(1264)
+ const { signOut } = useAuth()
+
+ return (
+
+
+
+
+
+
+ {upperSideTabs.map((tab) => (
+
+ {tab.displayedName}
+
+ ))}
+
+
+
+ Logout
+
+
+
+
+
+ )
+}
+
+const jsStyles = StyleSheet.create({
+ sideBar: {
+ minWidth: 72,
+ width: 72,
+ },
+
+ sidebarInner: {
+ alignItems: 'stretch',
+ borderRightColor: TabColorsStrings.lightGray,
+ borderRightWidth: 1,
+ height: '100%',
+ maxHeight: '100%',
+ minWidth: 72,
+ paddingBottom: 20,
+ paddingHorizontal: 12,
+ paddingTop: 8,
+ position: 'absolute',
+ width: 72,
+ },
+ sidebarInner2: {
+ alignItems: 'stretch',
+ flex: 1,
+ height: '100%',
+ justifyContent: 'space-between',
+ },
+
+ sidebarTabs: { flex: 1, gap: 4 },
+})
diff --git a/src/navigation/tabNavigator/components/SideBarTabItem.tsx b/src/navigation/tabNavigator/components/SideBarTabItem.tsx
new file mode 100644
index 00000000..e656e47e
--- /dev/null
+++ b/src/navigation/tabNavigator/components/SideBarTabItem.tsx
@@ -0,0 +1,116 @@
+import { Text } from '@bacons/react-views'
+import { Platform, StyleSheet, View } from 'react-native'
+
+import { TabBarItemWrapper } from './TabBarItemWrapper'
+import { TabColors, TabColorsStrings } from '../config'
+import { useWidth } from '../hooks'
+import { cns } from '../utils'
+
+import { Icon } from '~components'
+import { useColorScheme } from '~contexts'
+import cssStyles from '~styles'
+import { IconNames } from '~types/icon'
+
+export function SideBarTabItem({
+ children,
+ icon,
+ iconFocused,
+ name,
+ onPress,
+ params,
+}: {
+ children: string
+ icon: IconNames
+ iconFocused: IconNames
+ name: string
+ onPress?(): void
+ params?: Record
+}) {
+ const isLarge = useWidth(1264)
+ const { colorScheme } = useColorScheme()
+
+ return (
+
+ {({ focused, hovered }) => (
+
+
+
+
+
+
+ {children}
+
+
+ )}
+
+ )
+}
+
+const jsStyles = StyleSheet.create({
+ fontBold: { fontWeight: 'bold' },
+ sidebarIconContainer: Platform.select({
+ default: { padding: 0 },
+ web: {
+ transitionDuration: '150ms',
+ transitionProperty: ['transform'],
+ transitionTimingFunction: 'cubic-bezier(0.17, 0.17, 0, 1)',
+ },
+ }),
+ sidebarItemContainer: {
+ alignItems: 'center',
+ borderRadius: 8,
+ flexDirection: 'row',
+ padding: 8,
+ ...Platform.select({
+ web: {
+ transitionDuration: '200ms',
+ transitionProperty: ['background-color', 'box-shadow'],
+ },
+ }),
+ },
+ sidebarItemText: {
+ fontSize: 16,
+ lineHeight: 24,
+ marginLeft: 16,
+ marginRight: 16,
+ },
+ sidebarTabItem: {
+ paddingVertical: 4,
+ width: '100%',
+ },
+})
diff --git a/src/navigation/tabNavigator/components/TabBarItemWrapper.tsx b/src/navigation/tabNavigator/components/TabBarItemWrapper.tsx
new file mode 100644
index 00000000..2ac29c1e
--- /dev/null
+++ b/src/navigation/tabNavigator/components/TabBarItemWrapper.tsx
@@ -0,0 +1,43 @@
+import { Pressable } from '@bacons/react-views'
+import { Link } from 'expo-router'
+import { PressableStateCallbackType, ViewStyle } from 'react-native'
+
+import { useIsTabSelected } from '../hooks'
+import { TabbedNavigator } from '../tab-slot'
+
+export function TabBarItemWrapper({
+ children,
+ id,
+ name,
+ onPress,
+ params,
+ style,
+}: {
+ children?: (
+ props: PressableStateCallbackType & { hovered: boolean; focused: boolean }
+ ) => JSX.Element
+ id: string
+ name: string
+ onPress?: () => void
+ params?: Record
+ style?: ViewStyle
+}) {
+ const focused = useIsTabSelected(id)
+
+ if (onPress) {
+ return {(props) => children?.({ ...props, focused })}
+ }
+
+ if (name.startsWith('/') || name.startsWith('.')) {
+ return (
+
+ {(props) => children?.({ ...props, focused })}
+
+ )
+ }
+ return (
+
+ {(props) => children?.({ ...props, focused })}
+
+ )
+}
diff --git a/src/navigation/tabNavigator/components/index.ts b/src/navigation/tabNavigator/components/index.ts
new file mode 100644
index 00000000..29d34def
--- /dev/null
+++ b/src/navigation/tabNavigator/components/index.ts
@@ -0,0 +1,3 @@
+export * from './AppHeader'
+export * from './BottomBar'
+export * from './SideBar'
diff --git a/src/navigation/tabNavigator/config.ts b/src/navigation/tabNavigator/config.ts
new file mode 100644
index 00000000..5903b051
--- /dev/null
+++ b/src/navigation/tabNavigator/config.ts
@@ -0,0 +1,63 @@
+import { palette } from '~constants'
+import { IconNames } from '~types/icon'
+import { hex2rgba } from '~utils'
+
+type Tab = {
+ displayedName: string
+ icon: IconNames
+ iconFocused: IconNames
+ id: string
+ name: string
+ params?: Record
+}
+type Tabs = Tab[]
+
+// name with '/' at the begging will not be resolved as 'bottom tab', will be as usual screen
+export const upperSideTabs: Tabs = [
+ {
+ displayedName: 'Home',
+ icon: 'home-3-line',
+ iconFocused: 'home-3-fill',
+ id: 'home',
+ name: 'home',
+ },
+ {
+ displayedName: 'Example',
+ icon: 'aliens-line',
+ iconFocused: 'aliens-fill',
+ id: 'example',
+ name: 'example',
+ },
+ {
+ displayedName: 'Settings',
+ icon: 'settings-2-line',
+ iconFocused: 'settings-2-fill',
+ id: 'settings',
+ name: 'settings',
+ },
+ {
+ displayedName: 'Details',
+ icon: 'baidu-line',
+ iconFocused: 'baidu-fill',
+ id: 'details',
+ name: '/home/details',
+ params: { user: 'example@test.com' },
+ },
+]
+
+export const bottomSideTabs: Tabs = []
+
+export const bottomTabs: Tabs = [...upperSideTabs]
+
+export const TabColors: Record = {
+ tabIconDark: 'gray.700',
+ tabIconLight: 'gray.200',
+} as const
+
+export const TabColorsStrings = {
+ lightGray: palette.gray['300'],
+ lightGray50: hex2rgba(palette.gray['50'], 0.5),
+ tabTextDark: palette.gray['700'],
+ tabTextLight: palette.gray['300'],
+ transparent: 'transparent',
+} as const
diff --git a/src/navigation/tabNavigator/hooks/index.ts b/src/navigation/tabNavigator/hooks/index.ts
new file mode 100644
index 00000000..a84485b9
--- /dev/null
+++ b/src/navigation/tabNavigator/hooks/index.ts
@@ -0,0 +1,5 @@
+export * from './useIsTabSelected'
+export * from './useWidth'
+export * from './useContextRoute'
+export * from './useLinkBuilder'
+export * from './useNavigatorContext'
diff --git a/src/navigation/tabNavigator/hooks/useContextRoute.ts b/src/navigation/tabNavigator/hooks/useContextRoute.ts
new file mode 100644
index 00000000..4d3e206a
--- /dev/null
+++ b/src/navigation/tabNavigator/hooks/useContextRoute.ts
@@ -0,0 +1,30 @@
+import { Navigator } from 'expo-router'
+
+export function useContextRoute(name: string) {
+ const context = Navigator.useContext()
+
+ const { state, navigation, descriptors } = context
+
+ const route = state.routes.find((route) => {
+ return route.name === name
+ })
+
+ if (!route) {
+ console.warn(
+ `Could not find route with name: ${name}. Options: ${state.routes
+ .map((r) => r.name)
+ .join(', ')}`
+ )
+ }
+
+ if (!route) {
+ return null
+ }
+
+ return {
+ route,
+ target: state.key,
+ navigation,
+ descriptor: descriptors[route.key],
+ }
+}
diff --git a/src/navigation/tabNavigator/hooks/useIsTabSelected.ts b/src/navigation/tabNavigator/hooks/useIsTabSelected.ts
new file mode 100644
index 00000000..8fc52cdc
--- /dev/null
+++ b/src/navigation/tabNavigator/hooks/useIsTabSelected.ts
@@ -0,0 +1,10 @@
+import { useNavigatorContext } from './useNavigatorContext'
+
+export function useIsTabSelected(name: string): boolean {
+ const { navigation } = useNavigatorContext()
+
+ const state = navigation.getState()
+ const current = state.routes.find((_, i) => state.index === i)
+
+ return current?.name === name
+}
diff --git a/src/navigation/tabNavigator/hooks/useLinkBuilder.tsx b/src/navigation/tabNavigator/hooks/useLinkBuilder.tsx
new file mode 100644
index 00000000..79138fe0
--- /dev/null
+++ b/src/navigation/tabNavigator/hooks/useLinkBuilder.tsx
@@ -0,0 +1,70 @@
+import {
+ NavigationHelpers,
+ NavigationHelpersContext,
+ NavigationProp,
+ ParamListBase,
+} from '@react-navigation/core'
+import { LinkingContext } from '@react-navigation/native'
+import * as React from 'react'
+
+type NavigationObject = NavigationHelpers | NavigationProp
+
+type MinimalState = {
+ index: number
+ routes: { name: string; params?: object; state?: MinimalState }[]
+}
+
+const getRootStateForNavigate = (
+ navigation: NavigationObject,
+ state: MinimalState
+): MinimalState => {
+ const parent = navigation.getParent()
+
+ if (parent) {
+ const parentState = parent.getState()
+
+ return getRootStateForNavigate(parent, {
+ index: 0,
+ routes: [
+ {
+ ...parentState.routes[parentState.index],
+ state,
+ },
+ ],
+ })
+ }
+
+ return state
+}
+
+/**
+ * Build destination link for a navigate action.
+ * Useful for showing anchor tags on the web for buttons that perform navigation.
+ */
+export function useLinkBuilder() {
+ const navigation = React.useContext(NavigationHelpersContext)
+ const linking = React.useContext(LinkingContext)
+
+ const buildLink = React.useCallback(
+ (name: string, params?: object) => {
+ const state = navigation
+ ? getRootStateForNavigate(navigation, {
+ index: 0,
+ routes: [{ name, params }],
+ })
+ : // If we couldn't find a navigation object in context, we're at root
+ // So we'll construct a basic state object to use
+ {
+ index: 0,
+ routes: [{ name, params }],
+ }
+
+ const out = linking.options!.getPathFromState?.(state, linking.options!.config)
+
+ return out
+ },
+ [linking, navigation]
+ )
+
+ return buildLink
+}
diff --git a/src/navigation/tabNavigator/hooks/useNavigatorContext.ts b/src/navigation/tabNavigator/hooks/useNavigatorContext.ts
new file mode 100644
index 00000000..9b9204a8
--- /dev/null
+++ b/src/navigation/tabNavigator/hooks/useNavigatorContext.ts
@@ -0,0 +1,16 @@
+import { TabRouter } from '@react-navigation/routers'
+import { Navigator } from 'expo-router'
+
+export function useNavigatorContext() {
+ const context = Navigator.useContext()
+
+ if (process.env.NODE_ENV !== 'production') {
+ if (!(context.router.name === 'TabRouter' || context.router instanceof TabRouter)) {
+ throw new Error(
+ 'useTabbedSlot must be used inside a Navigator with a tab router: '
+ )
+ }
+ }
+
+ return context
+}
diff --git a/src/navigation/tabNavigator/hooks/useWidth.ts b/src/navigation/tabNavigator/hooks/useWidth.ts
new file mode 100644
index 00000000..093b9fcb
--- /dev/null
+++ b/src/navigation/tabNavigator/hooks/useWidth.ts
@@ -0,0 +1,12 @@
+import { Platform, useWindowDimensions } from 'react-native'
+
+export function useWidth(size: number): boolean {
+ const { width } = useWindowDimensions()
+ if (typeof window === 'undefined') {
+ return true
+ }
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
+ return false
+ }
+ return width >= size
+}
diff --git a/src/navigation/tabNavigator/navigator.tsx b/src/navigation/tabNavigator/navigator.tsx
new file mode 100644
index 00000000..9af562c9
--- /dev/null
+++ b/src/navigation/tabNavigator/navigator.tsx
@@ -0,0 +1,45 @@
+import { StyleSheet } from '@bacons/react-views'
+import React from 'react'
+import { Platform, View } from 'react-native'
+
+import { AppHeader, BottomBar, SideBar } from './components'
+import { useWidth } from './hooks'
+import { TabbedNavigator } from './tab-slot'
+import { cns } from './utils'
+
+import cssStyles from '~styles'
+
+export function ResponsiveNavigator() {
+ const isRowLayout = useWidth(768)
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const jsStyles = StyleSheet.create({
+ flex1: { flex: 1 },
+ flexGrow1: { flexGrow: 1 },
+})
diff --git a/src/navigation/tabNavigator/tab-slot.tsx b/src/navigation/tabNavigator/tab-slot.tsx
new file mode 100644
index 00000000..c53a2490
--- /dev/null
+++ b/src/navigation/tabNavigator/tab-slot.tsx
@@ -0,0 +1,136 @@
+// Like from Expo Router but with stored tab history.
+import { CommonActions } from '@react-navigation/native'
+import { TabRouter } from '@react-navigation/routers'
+import { Link, Navigator } from 'expo-router'
+import { Screen as RouterScreen } from 'expo-router/build/views/Screen'
+import * as React from 'react'
+import { GestureResponderEvent, StyleSheet, ViewStyle } from 'react-native'
+import { Screen, ScreenContainer } from 'react-native-screens'
+
+import { useContextRoute, useLinkBuilder, useNavigatorContext } from './hooks'
+
+export function TabbedNavigator(props: React.ComponentProps) {
+ return
+}
+
+export default function TabbedSlot({
+ detachInactiveScreens = true,
+ style,
+}: {
+ detachInactiveScreens?: boolean
+ style?: ViewStyle
+}) {
+ const { state, descriptors } = useNavigatorContext()
+ const focusedRouteKey = state.routes[state.index].key
+ const [loaded, setLoaded] = React.useState([focusedRouteKey])
+
+ if (!loaded.includes(focusedRouteKey)) {
+ setLoaded([...loaded, focusedRouteKey])
+ }
+
+ const { routes } = state
+
+ return (
+
+ {routes.map((route, index) => {
+ const descriptor = descriptors[route.key]
+ const {
+ freezeOnBlur,
+ lazy = true,
+ unmountOnBlur,
+ } = descriptor.options as unknown as {
+ lazy: boolean
+ unmountOnBlur?: boolean
+ freezeOnBlur: boolean
+ }
+ const isFocused = state.index === index
+
+ if (unmountOnBlur && !isFocused) {
+ return null
+ }
+
+ if (lazy && !loaded.includes(route.key) && !isFocused) {
+ // Don't render a lazy screen if we've never navigated to it
+ return null
+ }
+
+ const zIndex = { zIndex: isFocused ? 0 : -1 }
+
+ return (
+
+ {descriptor.render()}
+
+ )
+ })}
+
+ )
+}
+
+export function TabLink({
+ name,
+ params,
+ ...props
+}: { name: string; params?: Record } & Omit<
+ React.ComponentProps,
+ 'href' | 'onPress' | 'onLongPress'
+>) {
+ const buildLink = useLinkBuilder()
+ const ctxRoute = useContextRoute(name)
+
+ if (!ctxRoute) {
+ return null
+ }
+
+ const { route, target, navigation } = ctxRoute
+
+ const onPress = (
+ e: GestureResponderEvent | React.MouseEvent
+ ) => {
+ const event = navigation.emit({
+ type: 'tabPress',
+ target: route.key,
+ canPreventDefault: true,
+ })
+
+ // @ts-expect-error: event type does not contain defaultPrevented, which should be available here on web
+ if (!event.defaultPrevented) {
+ e.preventDefault()
+ navigation.dispatch({
+ ...CommonActions.navigate({ name: route.name, merge: true, params }),
+ target,
+ })
+ }
+ }
+
+ const onLongPress = () => {
+ navigation.emit({
+ type: 'tabLongPress',
+ target: route.key,
+ })
+ }
+
+ return
+}
+
+TabbedNavigator.Slot = TabbedSlot
+TabbedNavigator.Link = TabLink
+TabbedNavigator.Screen = RouterScreen
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ overflow: 'hidden',
+ },
+ screen: {
+ ...StyleSheet.absoluteFillObject,
+ overflow: 'hidden',
+ },
+})
diff --git a/src/navigation/tabNavigator/utils/cns.ts b/src/navigation/tabNavigator/utils/cns.ts
new file mode 100644
index 00000000..6ccb48de
--- /dev/null
+++ b/src/navigation/tabNavigator/utils/cns.ts
@@ -0,0 +1,6 @@
+export const cns = (
+ ...classes: (string | false | undefined | null)[]
+): Record => ({
+ $$css: true,
+ _: classes.filter(Boolean).join(' ') as unknown as string[],
+})
diff --git a/src/navigation/tabNavigator/utils/index.ts b/src/navigation/tabNavigator/utils/index.ts
new file mode 100644
index 00000000..817041e1
--- /dev/null
+++ b/src/navigation/tabNavigator/utils/index.ts
@@ -0,0 +1 @@
+export * from './cns'
diff --git a/src/providers/Providers.tsx b/src/providers/Providers.tsx
index dc5c6ce4..d9548d3c 100644
--- a/src/providers/Providers.tsx
+++ b/src/providers/Providers.tsx
@@ -1,7 +1,7 @@
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'
import { PortalProvider } from '@gorhom/portal'
import { QueryClientProvider, QueryClient } from '@tanstack/react-query'
-import { ReactNode } from 'react'
+import { FC, PropsWithChildren } from 'react'
import { StyleSheet } from 'react-native'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { SafeAreaProvider } from 'react-native-safe-area-context'
@@ -17,7 +17,7 @@ import { checkForUpdates } from '~utils'
const queryClient = new QueryClient({})
-export const Providers = ({ children }: { children: ReactNode }): JSX.Element => {
+export const Providers: FC = ({ children }) => {
useAppStateActive(checkForUpdates, false)
return (
diff --git a/src/screens/ApplicationInfoScreen.tsx b/src/screens/ApplicationInfoScreen.tsx
index 710ac0ed..a440352d 100644
--- a/src/screens/ApplicationInfoScreen.tsx
+++ b/src/screens/ApplicationInfoScreen.tsx
@@ -7,19 +7,24 @@ import { ScrollView, StyleSheet } from 'react-native'
import { Button, Text } from '~components'
import { isExpoGo } from '~constants'
-import { useCallback, usePreventGoBack, useTranslation } from '~hooks'
+import { useCallback, usePreventGoBack, useScreenOptions, useTranslation } from '~hooks'
const projectId = Constants.expoConfig?.extra?.eas?.projectId
export const ApplicationInfoScreen = (): JSX.Element => {
const { i18n, t } = useTranslation()
+
+ useScreenOptions({
+ title: t('navigation.screen_titles.application_info'),
+ })
+
usePreventGoBack()
const handleCopyPushToken = useCallback(async () => {
try {
if (!isExpoGo && !projectId) {
throw new Error(
- 'You must set `projectId` in eas build then value will be avaliable from Constants?.expoConfig?.extra?.eas?.projectId'
+ 'You must set `projectId` in eas build then value will be available from Constants?.expoConfig?.extra?.eas?.projectId'
)
}
const token = (
diff --git a/src/screens/ColorsScreen.tsx b/src/screens/ColorsScreen.tsx
index b6ab9051..75a4ad4d 100644
--- a/src/screens/ColorsScreen.tsx
+++ b/src/screens/ColorsScreen.tsx
@@ -1,4 +1,5 @@
import { Text, Center, ScrollView } from '~components'
+import { useScreenOptions, useTranslation } from '~hooks'
const colorsVariants: NestedKeys[] = [
'primary',
@@ -11,19 +12,27 @@ const colorsVariants: NestedKeys[] = [
'light',
]
-export const ColorsScreen = (): JSX.Element => (
-
- {colorsVariants.map((colorVariant) => (
-
- {colorVariant}
-
- ))}
-
-)
+export const ColorsScreen = (): JSX.Element => {
+ const { t } = useTranslation()
+
+ useScreenOptions({
+ title: t('navigation.screen_titles.colors'),
+ })
+
+ return (
+
+ {colorsVariants.map((colorVariant) => (
+
+ {colorVariant}
+
+ ))}
+
+ )
+}
diff --git a/src/screens/ComponentsScreen.tsx b/src/screens/ComponentsScreen.tsx
index 37a0124d..0debc739 100644
--- a/src/screens/ComponentsScreen.tsx
+++ b/src/screens/ComponentsScreen.tsx
@@ -1,7 +1,7 @@
import * as Linking from 'expo-linking'
import { Icon, Loader, Box, Text, Button, Center, ScrollView } from '~components'
-import { useCallback, useNotifications, useTranslation } from '~hooks'
+import { useCallback, useNotifications, useScreenOptions, useTranslation } from '~hooks'
const headingSizes = ['xs', 'sm', 'md', 'lg', '2xl', '3xl', '4xl'] as const
const loaderVariants = [
@@ -28,8 +28,13 @@ const loaderVariants = [
] as const
export const ComponentsScreen = (): JSX.Element => {
- const { notify } = useNotifications()
const { t } = useTranslation()
+ const { notify } = useNotifications()
+
+ useScreenOptions({
+ title: t('navigation.screen_titles.components'),
+ })
+
const testNotification = useCallback(
() =>
notify('info', {
diff --git a/src/screens/DataFromBeScreen_EXAMPLE.tsx b/src/screens/DataFromBeScreen_EXAMPLE.tsx
index b0daca05..89938ba4 100644
--- a/src/screens/DataFromBeScreen_EXAMPLE.tsx
+++ b/src/screens/DataFromBeScreen_EXAMPLE.tsx
@@ -1,11 +1,17 @@
import React, { useCallback } from 'react'
import { ListRenderItem, FlatList } from 'react-native'
-import { Loader, Center, Text, Box } from '~components'
-import { Spacer } from '~components/atoms'
+import { Loader, Center, Text, Box, Spacer } from '~components'
+import { useScreenOptions, useTranslation } from '~hooks'
import { useGetCity_EXAMPLE } from '~query-hooks'
import { TodoItem } from '~types/todos'
+
export const DataFromBeScreen_EXAMPLE = () => {
+ const { t } = useTranslation()
+
+ useScreenOptions({
+ title: t('navigation.screen_titles.data_from_be_screen_example'),
+ })
const { dataList, isFetchedDataAfterMount } = useGetCity_EXAMPLE()
const renderItem: ListRenderItem = useCallback(({ item: { title, id } }) => {
diff --git a/src/screens/DetailsScreen.tsx b/src/screens/DetailsScreen.tsx
index 5e4397dd..474ffaf5 100644
--- a/src/screens/DetailsScreen.tsx
+++ b/src/screens/DetailsScreen.tsx
@@ -1,16 +1,20 @@
+import { useLocalSearchParams } from 'expo-router'
+
import { useBottomSheet, Center, Text, Button, Box } from '~components'
-import { useState, useTranslation } from '~hooks'
+import { useScreenOptions, useState, useTranslation } from '~hooks'
-export const DetailsScreen = (props: DetailsScreenProps): JSX.Element => {
- const {
- route: { params },
- } = props
+export const DetailsScreen = (): JSX.Element => {
+ const { t } = useTranslation()
+ const localParams = useLocalSearchParams<{ user: string }>()
+
+ useScreenOptions({
+ title: t('navigation.screen_titles.details'),
+ })
const [items, setItems] = useState([])
const { bottomSheetComponentRenderFunction, presentBottomSheet } = useBottomSheet({
title: 'Details bottom sheet',
})
- const { t } = useTranslation()
const bottomSheet = bottomSheetComponentRenderFunction(
@@ -29,7 +33,7 @@ export const DetailsScreen = (props: DetailsScreenProps): JSX.Element => {
{t('details_screen.title')}
- {t('details_screen.screen_params', { params: JSON.stringify(params) })}
+ {t('details_screen.screen_params', { params: JSON.stringify(localParams) })}
{bottomSheet}
diff --git a/src/screens/ExamplesScreen.tsx b/src/screens/ExamplesScreen.tsx
index 0c8fc550..d95941e3 100644
--- a/src/screens/ExamplesScreen.tsx
+++ b/src/screens/ExamplesScreen.tsx
@@ -1,27 +1,24 @@
-import { Button, ScrollView } from '~components'
-import { useTranslation, useCallback } from '~hooks'
+import { useRouter } from 'expo-router'
-export const ExamplesScreen = (props: ExamplesScreenProps): JSX.Element => {
- const {
- navigation: { navigate },
- } = props
+import { Button, ScrollView } from '~components'
+import { useCallback, useTranslation, useScreenOptions } from '~hooks'
+export const ExamplesScreen = () => {
+ const { push } = useRouter()
const { t } = useTranslation()
- const goToApplicationInfo = useCallback(() => navigate('ApplicationInfo'), [navigate])
- const goToColors = useCallback(() => navigate('Colors'), [navigate])
- const goToComponents = useCallback(() => navigate('Components'), [navigate])
- const goToTypography = useCallback(() => navigate('Typography'), [navigate])
- const goToCityListScreen_EXAMPLE = useCallback(
- () => navigate('DataFromBeScreen_EXAMPLE'),
- [navigate]
- )
- const goToTestForm = useCallback(() => navigate('TestForm'), [navigate])
+ useScreenOptions({
+ title: t('navigation.screen_titles.examples'),
+ })
- const goToHomeStackDetails = useCallback(
- () => navigate('Details', { id: 'example-id' }),
- [navigate]
- )
+ const goToApplicationInfo = useCallback(() => push('/application-info'), [push])
+ const goToColors = useCallback(() => push('/example/colors'), [push])
+ const goToComponents = useCallback(() => push('/example/components'), [push])
+ const goToTypography = useCallback(() => push('/example/typography'), [push])
+ const goToCityListScreen_EXAMPLE = useCallback(() => push('/example/data-from-be'), [push])
+ const goToTestForm = useCallback(() => push('/example/test-form'), [push])
+
+ const goToHomeStackDetails = useCallback(() => push('/home/details'), [push])
return (
diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx
index 56c9e492..c08e12de 100644
--- a/src/screens/HomeScreen.tsx
+++ b/src/screens/HomeScreen.tsx
@@ -1,28 +1,34 @@
+import { router } from 'expo-router'
import { Image, StyleSheet } from 'react-native'
-import { Button, Center, Text } from '~components/atoms'
+import { Button, Center, Text } from '~components'
import { darkLogo, lightLogo } from '~constants'
import { useColorScheme } from '~contexts'
-import { useCallback, useTranslation } from '~hooks'
+import { useCallback, useScreenOptions, useTranslation } from '~hooks'
-export const HomeScreen = (props: HomeScreenProps): JSX.Element => {
- const {
- navigation: { navigate },
- } = props
+export const HomeScreen = () => {
const { t } = useTranslation()
+
+ useScreenOptions({
+ title: t('navigation.screen_titles.home'),
+ })
+
const { colorScheme } = useColorScheme()
const navigateToDetails = useCallback(() => {
- navigate('Details', { id: 'home-id' })
- }, [navigate])
+ router.navigate({
+ pathname: 'home/details',
+ params: { user: 'example@example.com' },
+ })
+ }, [])
return (
{t('hello')}
{t('thanks')}
diff --git a/src/screens/NotFoundScreen.tsx b/src/screens/NotFoundScreen.tsx
index 141fadf1..8726b1ec 100644
--- a/src/screens/NotFoundScreen.tsx
+++ b/src/screens/NotFoundScreen.tsx
@@ -1,8 +1,13 @@
import { Center, Text } from '~components/atoms'
-import { useTranslation } from '~hooks'
+import { useScreenOptions, useTranslation } from '~hooks'
export const NotFoundScreen = (): JSX.Element => {
const { t } = useTranslation()
+
+ useScreenOptions({
+ title: t('navigation.screen_titles.not_found'),
+ })
+
return (
{t('errors.screen_not_found')}
diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx
index 482510db..a5610fc3 100644
--- a/src/screens/SettingsScreen.tsx
+++ b/src/screens/SettingsScreen.tsx
@@ -1,12 +1,17 @@
import { Version, Spacer, Button, Center, Text, ScrollView } from '~components'
import { colorSchemesList } from '~constants'
import { useColorScheme } from '~contexts'
-import { useAuth, useCallback, useTranslation } from '~hooks'
+import { useAuth, useCallback, useScreenOptions, useTranslation } from '~hooks'
import { noop } from '~utils'
export const SettingsScreen = (): JSX.Element => {
const { t } = useTranslation()
const { setColorSchemeSetting, colorSchemeSetting } = useColorScheme()
+
+ useScreenOptions({
+ title: t('navigation.screen_titles.settings'),
+ })
+
const { signOut } = useAuth()
const handleColorSchemeSettingChange = useCallback(
diff --git a/src/screens/SignInScreen.test.tsx b/src/screens/SignInScreen.test.tsx
deleted file mode 100644
index e692fe3a..00000000
--- a/src/screens/SignInScreen.test.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { SignInScreen } from './SignInScreen'
-
-import { act, cleanup, fireEvent, render } from '~utils/testUtils'
-
-afterEach(cleanup)
-
-describe('SignInScreen', () => {
- it('should skip', () => {
- expect(true).toBe(true)
- })
-
- it('should render SignIn screen with the key elements', () => {
- const { getByTestId, getAllByRole } = render()
- const emailInput = getByTestId('emailInput')
- const passwordInput = getByTestId('passwordInput')
- const confirmationCheckbox = getByTestId('confirmCheckbox')
- const buttons = getAllByRole('button')
-
- expect(emailInput).toBeDefined()
- expect(passwordInput).toBeDefined()
- expect(confirmationCheckbox).toBeDefined()
- expect(buttons.length).toBe(2)
- })
-
- it('should display errors on required fields', async () => {
- const { findAllByText, getByTestId, update } = render()
- const emailInput = getByTestId('emailInput')
- const passwordInput = getByTestId('passwordInput')
- const signInButton = getByTestId('signInButton')
-
- expect(signInButton).toBeDefined()
- fireEvent.changeText(emailInput, '')
- fireEvent.changeText(passwordInput, '')
- fireEvent.press(signInButton)
-
- await act(async () => {
- update()
- const requiredInputs = await findAllByText('This field is required')
- expect(requiredInputs.length).toBe(2)
- })
- })
-
- it('should validate email format', async () => {
- const { getByTestId, getByText, update } = render()
-
- const emailInput = getByTestId('emailInput')
- fireEvent.changeText(emailInput, 'test@test')
-
- const signInButton = getByTestId('signInButton')
- expect(signInButton).toBeDefined()
-
- fireEvent.press(signInButton)
-
- await act(async () => {
- update()
- })
-
- expect(getByText('Incorrect e-mail address')).toBeTruthy()
- })
-})
diff --git a/src/screens/SignInScreen.tsx b/src/screens/SignInScreen.tsx
index a38277f0..aead3936 100644
--- a/src/screens/SignInScreen.tsx
+++ b/src/screens/SignInScreen.tsx
@@ -1,3 +1,4 @@
+import { useRouter } from 'expo-router'
import { StyleSheet, Image } from 'react-native'
import {
@@ -13,13 +14,17 @@ import {
} from '~components'
import { REGEX, darkLogo, lightLogo } from '~constants'
import { useColorScheme } from '~contexts'
-import { useCallback, useSignInForm, useNavigation, useTranslation, useEffect } from '~hooks'
+import { useCallback, useSignInForm, useTranslation, useEffect, useScreenOptions } from '~hooks'
export const SignInScreen = (): JSX.Element => {
- const { navigate } = useNavigation()
+ const { push } = useRouter()
const { t } = useTranslation()
const { colorScheme } = useColorScheme()
+ useScreenOptions({
+ title: t('navigation.screen_titles.sign_in'),
+ })
+
const { control, errors, submit, isSubmitting, setFocus } = useSignInForm()
useEffect(() => {
@@ -28,8 +33,8 @@ export const SignInScreen = (): JSX.Element => {
}, 500)
}, [setFocus])
- const navigateToSignUp = useCallback(() => navigate('SignUp'), [navigate])
- const navigateToAppInfo = useCallback(() => navigate('ApplicationInfo'), [navigate])
+ const navigateToSignUp = useCallback(() => push('/sign-up'), [push])
+ const navigateToAppInfo = useCallback(() => push('/application-info'), [push])
const focusPasswordInput = useCallback(() => setFocus('password'), [setFocus])
return (
@@ -46,16 +51,15 @@ export const SignInScreen = (): JSX.Element => {
/>
{
testID="emailInput"
/>
{
/>
@@ -100,7 +102,7 @@ export const SignInScreen = (): JSX.Element => {
{t('sign_in_screen.sign_in')}
- {t('sign_in_screen.dont_have_an_account')}
+ {t('sign_in_screen.do_not_have_an_account')}
{t('sign_in_screen.sign_up')}
diff --git a/src/screens/SignUpScreen.tsx b/src/screens/SignUpScreen.tsx
index d76f49e9..73b34dc5 100644
--- a/src/screens/SignUpScreen.tsx
+++ b/src/screens/SignUpScreen.tsx
@@ -1,13 +1,16 @@
import { useCallback, useEffect } from 'react'
-import { ControlledField, KeyboardAwareScrollView } from '~components'
-import { Button, Center, Spacer } from '~components/atoms'
+import { Button, Center, Spacer, ControlledField, KeyboardAwareScrollView } from '~components'
import { REGEX } from '~constants'
-import { useSignUpForm, useTranslation } from '~hooks'
+import { useScreenOptions, useSignUpForm, useTranslation } from '~hooks'
export const SignUpScreen = () => {
const { t } = useTranslation()
+ useScreenOptions({
+ title: t('navigation.screen_titles.sign_up'),
+ })
+
const { control, errors, submit, isSubmitting, setFocus } = useSignUpForm()
useEffect(() => {
@@ -23,29 +26,29 @@ export const SignUpScreen = () => {
{
message: t('form.invalid_email_format'),
},
}}
- isRequired
- mb={2}
/>
-
diff --git a/src/screens/TestFormScreen.tsx b/src/screens/TestFormScreen.tsx
index 18c5d8e2..f758a37c 100644
--- a/src/screens/TestFormScreen.tsx
+++ b/src/screens/TestFormScreen.tsx
@@ -2,7 +2,7 @@ import { Controller } from 'react-hook-form'
import { StyleSheet } from 'react-native'
import { ControlledField, KeyboardAwareScrollView, TextArea, Button, Text } from '~components'
-import { useMemo, useTestForm, useTranslation } from '~hooks'
+import { useMemo, useScreenOptions, useTestForm, useTranslation } from '~hooks'
const shoeSizes = [
'34',
@@ -25,6 +25,11 @@ const MUSICS = ['Metal', 'Heavy Metal', 'Rock', 'Pop', 'Rap']
export const TestFormScreen = (): JSX.Element => {
const { t } = useTranslation()
+
+ useScreenOptions({
+ title: t('navigation.screen_titles.colors'),
+ })
+
const { control, errors, submit, VALIDATION, setFocus } = useTestForm()
const INTERESTS = useMemo(
@@ -69,125 +74,113 @@ export const TestFormScreen = (): JSX.Element => {
{t('test_form.contact_data')}
- {t('test_form.additiona_comment')}
+ {t('test_form.additional_comment')}
(