$30 off During Our Annual Pro Sale. View Details »

React Native for Better or Worst

Radoslav Stankov
October 15, 2023
36

React Native for Better or Worst

Radoslav Stankov

October 15, 2023
Tweet

Transcript

  1. React Native
    for better or worst !

    View Slide

  2. "

    View Slide

  3. Radoslav Stankov
    @rstankov rstankov.com

    View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. # Side project

    View Slide

  8. View Slide

  9. - implement a mobile app $
    - one developer (me) !
    - support iOS % / Android &
    - ship it as fast as possible '

    View Slide

  10. Going native (swift/kotlin) wasn't an option

    View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. ( backed by Google
    ) uses Dart
    * cross platform
    + compiles to native machine code
    , custom components
    - battery included

    View Slide

  16. View Slide

  17. . backed by Facebook
    / uses Javascript (or TypeScript)
    0 uses React
    1 cross platform
    2 minimal
    3 native and JS bridge
    4 native components

    View Slide

  18. React Native in 2019 was 5
    React Native in 2021 was 6
    React Native in 2022 was 7
    React Native in 2023 still is 7

    View Slide

  19. View Slide

  20. React Native
    is fast enough
    has a rich ecosystem
    Flutter
    is faster
    has more batteries included
    I have done 3 React Native apps previously
    I know React/TypeScript/GraphQL extremely well
    I don't know Dart
    I don't know Flutter

    View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. Expo in 2020 was ... 8
    Expo in 2020 was ... 9
    Expo in 2022 was ... 7
    Expo in 2023 is ...:

    View Slide

  25. View Slide

  26. $ Tech Stack

    View Slide

  27. View Slide

  28. Architecture

    View Slide

  29. View Slide

  30. View Slide

  31. View Slide

  32. 0 Make common operations easy
    4 Have good code organization
    1 Isolate dependencies
    + Extensibility and reusability

    View Slide

  33. 0 Make common operations easy
    4 Have good code organization
    1 Isolate dependencies
    + Extensibility and reusability

    View Slide

  34. 2
    1 3
    Support Components Screens

    View Slide

  35. Support
    Components
    Screens

    View Slide

  36. 1) Support 2) Components 3) Screens

    View Slide

  37. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  38. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx
    1 Support
    Components
    Pages
    2
    3

    View Slide

  39. View Slide

  40. 3
    Screens

    View Slide

  41. View Slide

  42. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  43. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  44. {
    "expo": {
    "name": "Angry Building",
    "version": "1.48.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "splash": {
    "image": "./assets/splash.png",
    "resizeMode": "contain",
    "backgroundColor": "#252629"
    }
    },
    // ...
    }

    View Slide

  45. View Slide

  46. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  47. View Slide

  48. @react-navigation/native

    View Slide

  49. View Slide

  50. View Slide

  51. ... I still don't use it, because it was released fairly recently ;
    ... however the way the app is setup, transition should be easy <

    View Slide

  52. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  53. import screens from 'src/screens';
    import { NavigationContainer } from '@react-navigation/native';
    import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
    import { createStackNavigator } from '@react-navigation/stack';
    Sentry.init();
    const RootStack = createStackNavigator();
    export default function App() {
    return (















    View Slide

  54. export default function App() {
    return (
















    );
    }
    14 pt

    View Slide

  55. const Tab = createBottomTabNavigator();
    function TabsScreen() {
    return (

    name="buildingTab"
    component={Screen}
    initialParams={{ initialRoute: screens.home.name }}
    options={/* ... */}
    />
    name="apartmentTab"
    component={Screen}
    initialParams={{ initialRoute: screens.apartment.name }}
    options={/* ... */}
    />
    name="bulletinBoardTab"
    component={Screen}
    initialParams={{ initialRoute: screens.bulletinBoard.name }}
    options={/* ... */}
    />
    name="cashReserveTab"
    component={Screen}
    initialParams={{ initialRoute: screens.cashReserve.name }}

    View Slide

  56. />
    name="apartmentTab"
    component={Screen}
    initialParams={{ initialRoute: screens.apartment.name }}
    options={/* ... */}
    />
    name="bulletinBoardTab"
    component={Screen}
    initialParams={{ initialRoute: screens.bulletinBoard.name }}
    options={/* ... */}
    />
    name="cashReserveTab"
    component={Screen}
    initialParams={{ initialRoute: screens.cashReserve.name }}
    options={/* ... */}
    />
    name="issuesTab"
    component={Screen}
    initialParams={{ initialRoute: screens.issues.name }}
    options={/* ... */}
    />

    );
    }

    View Slide

  57. const ScreenStack = createStackNavigator();
    function Screen(props) {
    const initialRoute = props.route.params.initialRoute;
    return (




















    );
    }

    View Slide

  58. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  59. import apartment from './apartment';
    import home from './home';
    // ...
    export default {
    apartment,
    home,
    // ...
    };

    View Slide

  60. Screen States
    Layout

    View Slide

  61. Loading State
    Screen States
    Layout

    View Slide

  62. Loading State
    Error State
    Screen States
    Layout

    View Slide

  63. Loading State
    Not Found Error
    Server Error
    Authorization
    Error
    Authentication
    Error
    Error State
    Screen States
    Layout

    View Slide

  64. Loading State
    Not Found Error
    Server Error
    Authorization
    Error
    Authentication
    Error
    Error State
    Loaded State
    Screen States
    Layout

    View Slide

  65. Loading State
    Not Found Error
    Server Error
    Authorization
    Error
    Authentication
    Error
    Error State
    Loaded State
    render
    Screen States
    Layout

    View Slide

  66. interface IOptions {
    name: string;
    query?: DocumentNode;
    queryVariables?: object | ((params: P) => object);
    queryRefreshOnShow?: boolean;
    component: IComponent;
    type?: keyof typeof layouts;
    headerTitle?: ITranslation;
    background: IBackground;
    }
    interface IScreen {
    name: string;
    options: StackNavigationOptions;
    component: any;
    }
    export default function createScreen(options: IOptions): IScreen
    // ...
    }

    View Slide

  67. screens/[name]/index.ts (createScreen) -> screens/index.ts -> app.ts

    View Slide

  68. export default createScreen({
    name: 'home',
    type: 'plain',
    query: QUERY,
    background: 'black',
    component({ data, fetchMore, refetch }) {
    usePushNotificationsRegister();
    usePushNotificationHandle(data.viewer);
    return (


    refetch={refetch}
    fetchMore={fetchMore}
    building={data.building}
    />


    );
    },
    });

    View Slide

  69. ▾ src/
    ▾ screens/
    ▾ home/
    ▸ Status/
    background.png
    BuildingApartmentsList.tsx
    Header.tsx
    index.tsx
    Query.ts
    SelectEntrancePicker.tsx

    View Slide

  70. http://graphql.org/

    View Slide

  71. View Slide

  72. graphql-codegen --config codegen.yml

    View Slide

  73. components/ApartmentStatus/Fragment.tsx
    import { gql } from '@apollo/client';
    export default gql`
    fragment IApartmentStatusFragment on Apartment {
    id
    number
    name
    overdueAmount
    }
    `;

    View Slide

  74. types/grahpql.ts
    export type IApartmentStatusFragment = {
    __typename: 'Apartment';
    id: string;
    number?: string | null;
    name?: string | null;
    overdueAmount: number;
    };

    View Slide

  75. import { IApartmentStatusFragment } from '~/types/graphql';

    View Slide

  76. import { gql } from '@apollo/client';
    import IHomeScreenStatusFragment from './Status/Fragment';
    import IApartmentStatusFragment from 'src/components/ApartmentStatus/Fragment';
    export default gql`
    query IHomeScreen($cursor: String) {
    building {
    id
    name
    apartments(first: 100, after: $cursor) {
    nodes {
    id
    name
    floor
    isViewerSelected
    ...IApartmentStatusFragment
    }
    pageInfo {
    hasNextPage
    endCursor
    }
    }
    ...IHomeScreenStatusFragment
    }
    }
    ${IHomeScreenStatusFragment}
    ${IApartmentStatusFragment}
    `;
    screens/home/Query.ts

    View Slide

  77. Query
    Fragment Fragment
    Fragment
    Fragment
    Fragment
    Fragment

    View Slide

  78. Screen
    Component Component
    Component
    Component
    Component
    Component

    View Slide

  79. View Slide

  80. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  81. import routes from '~/routes';

    routes.back;
    routes.home;
    routes.bulletinBoardPostUpdate(post);

    View Slide


  82. View Slide

  83. import { useNavigation } from '@react-navigation/native';
    import screens from 'src/screens';
    type IScreenName = keyof typeof screens;
    export type IRoute =
    | { transition: 'goBack' }
    | { screen: IScreenName; params?: IParams; root?: boolean }
    | {
    screen: 'buildingTab' | 'apartmentTab' | 'cashReserveTab' | 'issuesTab';
    params?: IParams;
    root: true;
    };
    const routes = {
    back: { transition: 'goBack' } as IRoute,
    home: { root: true, screen: 'buildingTab' } as IRoute,
    // ...
    bulletinBoardPostUpdate(post: { id: string }) {
    return {
    screen: 'bulletinBoardPostUpdate',
    params: { post },
    } as IRoute;
    },
    };

    View Slide

  84. import { useNavigation } from '@react-navigation/native';
    import screens from 'src/screens';
    type IScreenName = keyof typeof screens;
    export type IRoute =
    | { transition: 'goBack' }
    | { screen: IScreenName; params?: IParams; root?: boolean }
    | {
    screen: 'buildingTab' | 'apartmentTab' | 'cashReserveTab' | 'issuesTab';
    params?: IParams;
    root: true;
    };
    const routes = {
    back: { transition: 'goBack' } as IRoute,
    home: { root: true, screen: 'buildingTab' } as IRoute,
    // ...
    bulletinBoardPostUpdate(post: { id: string }) {
    return {
    screen: 'bulletinBoardPostUpdate',
    params: { post },
    } as IRoute;
    },
    };

    View Slide

  85. import { useNavigation } from '@react-navigation/native';
    import screens from 'src/screens';
    type IScreenName = keyof typeof screens;
    export type IRoute =
    | { transition: 'goBack' }
    | { screen: IScreenName; params?: IParams; root?: boolean }
    | {
    screen: 'buildingTab' | 'apartmentTab' | 'cashReserveTab' | 'issuesTab';
    params?: IParams;
    root: true;
    };
    const routes = {
    back: { transition: 'goBack' } as IRoute,
    home: { root: true, screen: 'buildingTab' } as IRoute,
    // ...
    bulletinBoardPostUpdate(post: { id: string }) {
    return {
    screen: 'bulletinBoardPostUpdate',
    params: { post },
    } as IRoute;
    },
    };

    View Slide

  86. import { useNavigation } from '@react-navigation/native';
    import screens from 'src/screens';
    type IScreenName = keyof typeof screens;
    export type IRoute =
    | { transition: 'goBack' }
    | { screen: IScreenName; params?: IParams; root?: boolean }
    | {
    screen: 'buildingTab' | 'apartmentTab' | 'cashReserveTab' | 'issuesTab';
    params?: IParams;
    root: true;
    };
    const routes = {
    back: { transition: 'goBack' } as IRoute,
    home: { root: true, screen: 'buildingTab' } as IRoute,
    // ...
    bulletinBoardPostUpdate(post: { id: string }) {
    return {
    screen: 'bulletinBoardPostUpdate',
    params: { post },
    } as IRoute;
    },
    };

    View Slide

  87. | { screen: IScreenName; params?: IParams; root?: boolean }
    | {
    screen: 'buildingTab' | 'apartmentTab' | 'cashReserveTab' | 'issuesTab';
    params?: IParams;
    root: true;
    };
    const routes = {
    back: { transition: 'goBack' } as IRoute,
    home: { root: true, screen: 'buildingTab' } as IRoute,
    // ...
    bulletinBoardPostUpdate(post: { id: string }) {
    return {
    screen: 'bulletinBoardPostUpdate',
    params: { post },
    } as IRoute;
    },
    };
    export default routes;
    export interface INavigation {
    navigate: (screen: string, params?: IParams) => void;
    goBack: VoidFunction;
    }

    View Slide

  88. | { screen: IScreenName; params?: IParams; root?: boolean }
    | {
    screen: 'buildingTab' | 'apartmentTab' | 'cashReserveTab' | 'issuesTab';
    params?: IParams;
    root: true;
    };
    const routes = {
    back: { transition: 'goBack' } as IRoute,
    home: { root: true, screen: 'buildingTab' } as IRoute,
    // ...
    bulletinBoardPostUpdate(post: { id: string }) {
    return {
    screen: 'bulletinBoardPostUpdate',
    params: { post },
    } as IRoute;
    },
    };
    export default routes;
    export interface INavigation {
    navigate: (screen: string, params?: IParams) => void;
    goBack: VoidFunction;
    }

    View Slide

  89. import { useNavigation } from '@react-navigation/native';
    import screens from 'src/screens';
    type IScreenName = keyof typeof screens;
    export type IRoute =
    | { transition: 'goBack' }
    | { screen: IScreenName; params?: IParams; root?: boolean }
    | {
    screen: 'buildingTab' | 'apartmentTab' | 'cashReserveTab' | 'issuesTab';
    params?: IParams;
    root: true;
    };
    const routes = {
    back: { transition: 'goBack' } as IRoute,
    home: { root: true, screen: 'buildingTab' } as IRoute,
    // ...
    bulletinBoardPostUpdate(post: { id: string }) {
    return {
    screen: 'bulletinBoardPostUpdate',
    params: { post },
    } as IRoute;
    },
    };

    View Slide

  90. export interface INavigation {
    navigate: (screen: string, params?: IParams) => void;
    goBack: VoidFunction;
    }
    export function navigate(navigation: INavigation, to: IRoute) {
    if ('transition' in to) {
    navigation.goBack();
    } else if (to.root) {
    navigation.navigate('Main', to);
    } else {
    navigation.navigate(to.screen, to.params);
    }
    }
    export function useNavigate() {
    const navigation: any = useNavigation();
    const fn = React.useCallback(
    (to: IRoute) => {
    navigate(navigation, to);
    },
    [navigation],
    );
    return fn;
    }

    View Slide

  91. import routes, { useNavigate } from '~/routes';
    export function MyComponent() {
    const navigate = useNavigate();
    const onPress = () => {
    navigate(routes.home);
    }
    return (

    Visit product

    );
    }

    View Slide

  92. 2
    Components

    View Slide

  93. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  94. View Slide

  95. = Component as directory
    components/
    PublicComponent/
    PrivateSubComponent/
    Fragment.graphql
    Mutation.graphql
    index.ts
    styles.ts
    utils.ts

    View Slide

  96. Only use this component

    View Slide

  97. to={routes.profile(profile)} />

    View Slide

  98. to={routes.profile(profile)} />
    onPress={onClickReturnsPromise} />

    View Slide

  99. to={routes.profile(profile)} />
    onPress={onClickReturnsPromise} />
    onPress={onClickReturnsPromise}
    confirm="Are you sure?"
    requireLogin={true} />

    View Slide

  100. to={routes.profile(profile)} />
    onPress={onClickReturnsPromise} />
    onPress={onClickReturnsPromise}
    confirm="Are you sure?"
    requireLogin={true} />
    mutation={MUTATION}
    input={input}
    onMutate={onMutate} />

    View Slide





  101. View Slide

  102. View Slide

  103. {...}
    {...}

    View Slide

  104. 1
    Support

    View Slide

  105. View Slide

  106. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  107. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  108. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  109. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  110. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  111. > ? @

    View Slide

  112. i18next

    View Slide

  113. ▾ src/
    ▾ translations/
    bg.json
    en.json
    es.json
    index.ts

    View Slide

  114. import i18n from 'i18next';
    import captureError from 'src/utils/captureError';
    import { isProduction } from 'src/config';
    import bg from './bg.json';
    import en from './en.json';
    import es from './es.json';
    // NOTE(rstankov): Documentation https://www.i18next.com/overview/configuration-optio
    i18n.init({
    resources: {
    bg: {
    translation: bg,
    },
    en: {
    translation: en,
    },
    es: {
    translation: es,
    },
    },
    lng: 'bg',
    saveMissing: true,
    missingKeyHandler: (_lngs: any, _ns: string, key: string, _fallbackValue: any) => {
    if (isProduction) {
    captureError(`Key not found t('${key}')`);

    View Slide

  115. import i18n from 'i18next';
    import captureError from 'src/utils/captureError';
    import { isProduction } from 'src/config';
    import bg from './bg.json';
    import en from './en.json';
    import es from './es.json';
    // NOTE(rstankov): Documentation https://www.i18next.com/overview/configuration-optio
    i18n.init({
    resources: {
    bg: {
    translation: bg,
    },
    en: {
    translation: en,
    },
    es: {
    translation: es,
    },
    },
    lng: 'bg',
    saveMissing: true,
    missingKeyHandler: (_lngs: any, _ns: string, key: string, _fallbackValue: any) => {
    if (isProduction) {
    captureError(`Key not found t('${key}')`);

    View Slide

  116. import i18n from 'i18next';
    import captureError from 'src/utils/captureError';
    import { isProduction } from 'src/config';
    import bg from './bg.json';
    import en from './en.json';
    import es from './es.json';
    // NOTE(rstankov): Documentation https://www.i18next.com/overview/configuration-optio
    i18n.init({
    resources: {
    bg: {
    translation: bg,
    },
    en: {
    translation: en,
    },
    es: {
    translation: es,
    },
    },
    lng: 'bg',
    saveMissing: true,
    missingKeyHandler: (_lngs: any, _ns: string, key: string, _fallbackValue: any) => {
    if (isProduction) {
    captureError(`Key not found t('${key}')`);

    View Slide

  117. // NOTE(rstankov): Documentation https://www.i18next.com/overview/configuration-optio
    i18n.init({
    resources: {
    bg: {
    translation: bg,
    },
    en: {
    translation: en,
    },
    es: {
    translation: es,
    },
    },
    lng: 'bg',
    saveMissing: true,
    missingKeyHandler: (_lngs: any, _ns: string, key: string, _fallbackValue: any) => {
    if (isProduction) {
    captureError(`Key not found t('${key}')`);
    return key;
    }
    throw new Error(`Key not found t('${key}')`);
    },
    compatibilityJSON: 'v3',
    });

    View Slide

  118. // NOTE(rstankov): Documentation https://www.i18next.com/overview/configuration-optio
    i18n.init({
    resources: {
    bg: {
    translation: bg,
    },
    en: {
    translation: en,
    },
    es: {
    translation: es,
    },
    },
    lng: 'bg',
    saveMissing: true,
    missingKeyHandler: (_lngs: any, _ns: string, key: string, _fallbackValue: any) => {
    if (isProduction) {
    captureError(`Key not found t('${key}')`);
    return key;
    }
    throw new Error(`Key not found t('${key}')`);
    },
    compatibilityJSON: 'v3',
    });

    View Slide

  119. // NOTE(rstankov): Documentation https://www.i18next.com/overview/configuration-optio
    i18n.init({
    resources: {
    bg: {
    translation: bg,
    },
    en: {
    translation: en,
    },
    es: {
    translation: es,
    },
    },
    lng: 'bg',
    saveMissing: true,
    missingKeyHandler: (_lngs: any, _ns: string, key: string, _fallbackValue: any) => {
    if (isProduction) {
    captureError(`Key not found t('${key}')`);
    return key;
    }
    throw new Error(`Key not found t('${key}')`);
    },
    compatibilityJSON: 'v3',
    });

    View Slide

  120. captureError(`Key not found t('${key}')`);
    return key;
    }
    throw new Error(`Key not found t('${key}')`);
    },
    compatibilityJSON: 'v3',
    });
    export type ITranslation = keyof typeof bg;
    export type ILanguage = 'bg' | 'en' | 'es';
    export default i18n.t as (key: keyof typeof bg, interpolations?: any) => string;
    export function changeLanguage(newLanguage: ILanguage) {
    if (i18n.language === newLanguage) {
    return;
    }
    return i18n.changeLanguage(newLanguage);
    }

    View Slide

  121. export type ITranslation = keyof typeof bg;
    export type ILanguage = 'bg' | 'en' | 'es';
    export default i18n.t as (key: keyof typeof bg, interpolations?: any) => string;
    export function changeLanguage(newLanguage: ILanguage) {
    if (i18n.language === newLanguage) {
    return;
    }
    return i18n.changeLanguage(newLanguage);
    }

    View Slide

  122. ▾ src/
    ▸ components/
    ▸ hooks/
    ▸ screens/
    ▸ styles/
    ▸ translations/
    ▸ types/
    ▸ utils/
    config.ts
    routes.ts
    app.json
    App.tsx

    View Slide

  123. import * as React from 'react';
    import { View, StyleSheet } from 'react-native';
    import s from 'src/styles';
    import Logo from 'src/components/Logo';
    export default React.memo(function Ribbon() {
    return (
    <>






    >
    );
    });
    const styles = StyleSheet.create({
    container: {
    backgroundColor: s.colors.black,
    },
    view: {
    marginTop: 10,
    borderTopLeftRadius: s.radius,
    borderTopRightRadius: s.radius,
    width: '100%',

    View Slide

  124. import * as React from 'react';
    import { View, StyleSheet } from 'react-native';
    import s from 'src/styles';
    import Logo from 'src/components/Logo';
    export default React.memo(function Ribbon() {
    return (
    <>






    >
    );
    });
    const styles = StyleSheet.create({
    container: {
    backgroundColor: s.colors.black,
    },
    view: {
    marginTop: 10,
    borderTopLeftRadius: s.radius,
    borderTopRightRadius: s.radius,
    width: '100%',

    View Slide

  125. import * as React from 'react';
    import { View, StyleSheet } from 'react-native';
    import s from 'src/styles';
    import Logo from 'src/components/Logo';
    export default React.memo(function Ribbon() {
    return (
    <>






    >
    );
    });
    const styles = StyleSheet.create({
    container: {
    backgroundColor: s.colors.black,
    },
    view: {
    marginTop: 10,
    borderTopLeftRadius: s.radius,
    borderTopRightRadius: s.radius,
    width: '100%',

    View Slide

  126. });
    const styles = StyleSheet.create({
    container: {
    backgroundColor: s.colors.black,
    },
    view: {
    marginTop: 10,
    borderTopLeftRadius: s.radius,
    borderTopRightRadius: s.radius,
    width: '100%',
    height: s.radius * 2,
    marginBottom: -s.radius,
    backgroundColor: s.colors.white,
    },
    topBackground: {
    position: 'absolute',
    top: -500,
    height: 500,
    right: 0,
    left: 0,
    backgroundColor: s.colors.black,
    alignItems: 'center',
    justifyContent: 'flex-end',
    paddingBottom: s.spacing.l,
    },
    });

    View Slide

  127. const styles = StyleSheet.create({
    container: {
    backgroundColor: s.colors.black,
    },
    view: {
    marginTop: 10,
    borderTopLeftRadius: s.radius,
    borderTopRightRadius: s.radius,
    width: '100%',
    height: s.radius * 2,
    marginBottom: -s.radius,
    backgroundColor: s.colors.white,
    },
    topBackground: {
    position: 'absolute',
    top: -500,
    height: 500,
    right: 0,
    left: 0,
    backgroundColor: s.colors.black,
    alignItems: 'center',
    justifyContent: 'flex-end',
    paddingBottom: s.spacing.l,
    },
    });

    View Slide




  128. View Slide




  129. View Slide

  130. import variables from 'src/styles';
    export type ISpacing = keyof typeof spacing;
    export interface ISpacingProps {
    marginBottom?: ISpacing | null;
    marginHorizontal?: ISpacing | null;
    marginLeft?: ISpacing | null;
    marginRight?: ISpacing | null;
    marginTop?: ISpacing | null;
    marginVertical?: ISpacing | null;
    margin?: ISpacing | null;
    padding?: ISpacing | null;
    paddingBottom?: ISpacing | null;
    paddingHorizontal?: ISpacing | null;
    paddingLeft?: ISpacing | null;
    paddingRight?: ISpacing | null;
    paddingTop?: ISpacing | null;
    paddingVertical?: ISpacing | null;
    }
    export function spacingStyle(props: ISpacingProps, style: any = {}) {
    if (props.marginTop) { style.marginTop = variables.spacing[props.marginTop]; }
    // ...
    return style;
    }

    View Slide

  131. import { spacingStyle, ISpacing } from '~/styles/spacing';
    interface IProps extends ISpacingProps {
    someProps: any,
    // ...
    }
    function MyComponent(props) {
    // ...
    return (

    {/* ... */}

    );
    }

    View Slide

  132. View Slide

  133. View Slide

  134. View Slide

  135. Thanks A

    View Slide