【11】React Nativeでテキストエディタを作ってみる!【メニューエリア作成編】

こんにちは!かたつむり(@Katatumuri_nyan)です!

Reactを触ってみて、サイト的なものは作れるようになりました(*´ω`)
そこで、次はReactNativeを触ってみようと思い、簡単なテキストエディタを作成しようと企んでおります(笑)

今回は、メニューエリアを作成していきます!
機能を沢山付けるメニューエリアが一番大変なので、頑張りますw

GitHubでソースコードを管理しています!

最初から見る↓
【1】React Nativeでテキストエディタを作ってみる!【下調べ編】【1】React Nativeでテキストエディタを作ってみる!【下調べ編】

前回を見る↓
【10】React Nativeでテキストエディタを作ってみる!【ナビ・エディタエリア作成編】【10】React Nativeでテキストエディタを作ってみる!【ナビ・エディタエリア作成編】

メニューエリアを作成

image 1
↑この左側に表示されるメニューエリアを作っていきます!

デザイン段階では、テキストエリアの上に被るように表示する予定でした。
しかし今回は、利便性を考えて、テキストエリアと並ぶように表示したいと思います!

Menu.jsの作成

picture 6
↑こんな感じで左側にメニューエリアを作成しました。

コードは↓このようになりました。

import React, { useContext } from 'react';
import { View, Text} from 'react-native';
import { useTheme } from 'react-native-elements';
import { ContextObject } from '../../modules/context';


export default function Menu() {
    const { theme } = useTheme();
    const {
    } = useContext(ContextObject)

    const styles = {
        menu:{
            width: 280,
            backgroundColor: theme.main.secondBackgroundColor,
            borderRadius: 20,
            marginRight: 10,
            padding:10,
            paddingTop: 20,
            flexDirection: 'column'
        },
        title:{
            color: theme.menu.titleColor,
            textAlign: 'center'
        }
    }

    return (
        <View style={styles.menu}>
            <Text style={styles.title}>設定</Text>
        </View>
    )
}

メニューエリア開閉機能の作成

左から右にスワイプすることで、メニューエリアを開閉させたいと思います(*´ω`)

// Main.js
<PanGestureHandler onGestureEvent={(event) => { onSwipeEvent(event) }}>
    <View style={styles.wrap}>
    {isMenuOpen ? <Menu />:<View/>}
      <EditorArea/>
    </View>
</PanGestureHandler>

Main.jsで↑このようにラップしました。

// Main.js
if (lefghtArea && swipeX < 0) {
  // (←)画面左半分を右から左にスワイプした時
  setIsMenuOpen(false)
  setAbsoluteX(absoluteX)
} else if (lefghtArea && swipeX > 0) {
  // (→)画面左半分を左から右にスワイプした時
  setIsMenuOpen(true)
  setAbsoluteX(absoluteX)
}

また、↑のように設定して、開閉できるようになりました(^▽^)/

あと、メニューエリアが開いていると、プレビューエリアの大きさが変わるので、スワイプ判定を変えました!

メニューボタンの作成

picture 7
↑このようにボタンを作成しました(*´ω`)

色々機能をつけるので、とりあえずシンプルなボタンにしました。

// MenuBtn.js
import React, { useContext } from 'react';
import { View ,Text} from 'react-native';
import { useTheme } from 'react-native-elements';
import { Icon } from 'react-native-elements';
import { ContextObject } from '../../../modules/context';


export default function MenuBtn() {
    const { theme } = useTheme();
    const {
    } = useContext(ContextObject)

    const styles={
        wrap: {
            width: '100%',
            height: 46,
            marginTop: 10,
            backgroundColor: theme.menuBtn.BackgroundColor,
            borderRadius: 20,
            flexDirection: 'row',
            alignItems: 'center',
            paddingLeft:10,
            paddingRight: 10
        },
        icon:{
            color: theme.menuBtn.iconColor,
            marginRight: 10,
            fontSize: 28
        },
        btnText:{
            color: theme.menuBtn.TextColor,
            fontSize: 18
        }
    }

    return (
        <View style={styles.wrap}>
            <Icon
            name='settings'
            iconStyle={styles.icon}
            />
            <Text style={styles.btnText}>ボタン</Text>
        </View>
    )
}
// MenuBtnChild.js
import React, { useContext } from 'react';
import { View ,Text} from 'react-native';
import { useTheme } from 'react-native-elements';
import { Icon } from 'react-native-elements';
import { ContextObject } from '../../../modules/context';

export default function MenuBtnChild() {
    const { theme } = useTheme();
    const {
    } = useContext(ContextObject)

    const styles={
        wrap: {
            alignSelf: 'flex-end',
            width: '90%',
            height: 46,
            marginTop: 10,
            backgroundColor: theme.menuBtnChild.BackgroundColor,
            borderColor: theme.menuBtnChild.BoderColor,
            borderStyle: 'solid',
            borderWidth:3,
            borderRadius: 20,
            flexDirection: 'row',
            alignItems: 'center',
            paddingLeft: 10,
            paddingRight: 10
        },
        icon:{
            color: theme.menuBtn.iconColor,
            marginRight: 10,
            fontSize: 28
        },
        btnText:{
            color: theme.menuBtn.TextColor,
            fontSize: 18
        }
    }

    return (
        <View style={styles.wrap}>
            <Icon
            name='settings'
            iconStyle={styles.icon}
            />
            <Text style={styles.btnText}>ボタン</Text>
        </View>
    )
}

ナビの開閉の連動

picture 8
↑こんな感じで、メニューが開いたとき、ナビも開いてほしいので、連動させます。

Nav.jsで以下のようにしました。

{isNavOpen | isMenuOpen?
  <NavOpened color={theme.nav.iconColor} />:
  <NavClosed color={theme.nav.iconColor} onPress={onNavOpen} />
}

ナビボタンを押したときの動作設定

picture 8
↑このナビボタンを押したときに、メニューが開いてほしいので、機能を付けていきます!

// Nav.js
function onPress(icon){
    setWhichMenuOpen(icon);
  }

↑この様な関数を作成し、context.jsでデータのやりとりをすることにしました。

押したボタンによるコンポーネントのきりかえ

// Menu.js
function WhichMenu(params) {
    const { theme } = useTheme();
    const {
        whichMenuOpen
    } = useContext(ContextObject)

    switch (whichMenuOpen) {
        case 'settings':
            return <Settings />
            break;

        case 'folder':
            return <Folder />
            break;

        case 'file-upload':
            return <FileUpload />
            break;

        default:
            return <Settings />
            break;
    }
}

↑このように、whichMenuOpenの値によって表示するコンポーネントを変えます。

このあたりの挙動は、もうちょっと変更したい部分があるので、後で変更予定です!

メニュー内のボタンを押したときの挙動追加

設定メニュー

picture 9
↑左のように表示したいのですが、ボタンを押すと右のように下に開くようにしたいです。
これを設定していきます!

↑このようにできました!

import React, { useContext, useState } from 'react';
import { View, Text} from 'react-native';
import { useTheme } from 'react-native-elements';
import { ContextObject } from '../../../modules/context';
import MenuBtn from '../_components/MenuBtn';
import MenuBtnChild from '../_components/MenuBtnChild';
import MenuTitle from '../_components/MenuTitle';

export default function Settings(props) {
    const { theme } = useTheme();
    const {
    } = useContext(ContextObject)

    const [isThemeMenuBtnOpen, setThemeMenuBtnOpen]=useState(false)
    const [isPreviewMenuBtnOpen, setPreviewMenuBtnOpen] = useState(false)
    const [isAutoSaveMenuBtnOpen, setAutoSaveMenuBtnOpen] = useState(false)

    const styles = {
        menu: {
        },
    }

    function onPress(is, set) {
        { is ? set(false) : set(true) }
    }

    return (
        <View style={styles.menu}>
            <MenuTitle>設定</MenuTitle>
            <MenuBtn 
                name='テーマ' 
                onPress={() => { onPress(isThemeMenuBtnOpen, setThemeMenuBtnOpen) }}
            />
            {isThemeMenuBtnOpen?<MenuBtnChild name='test'/>:<View/>}
            <MenuBtn
                name='プレビュー'
                onPress={() => { onPress(isPreviewMenuBtnOpen, setPreviewMenuBtnOpen) }}
            />
            {isPreviewMenuBtnOpen ? <MenuBtnChild name='てす' /> : <View />}
        </View>
    )
}

↑コードはこのようになりました。
MenuBtnMenuBtnChildをコンポーネントとしてまとめようか悩みましたが、ちょっと保留です。

とりあえず、設定メニューでスイッチコンポーネントなども使うので、まとめないことにします。

エクスポートメニュー

picture 10
↑このようなかんじになります!
エクスポートメニューでは、開くようにはせず、押すだけのボタンにします。

import React, { useContext, useEffect } from 'react';
import { View, Text } from 'react-native';
import { ContextObject } from '../../../modules/context';
import MenuBtn from '../_components/MenuBtn';
import MenuTitle from '../_components/MenuTitle';

export default function Export() {
    const {
        setIsMenuOpen
    } = useContext(ContextObject)

    function onPress() {
        setIsMenuOpen(false)
    }

    return (
    <View>
        <MenuTitle>エクスポート</MenuTitle>
        <MenuBtn
            name='Markdown'
            onPress={onPress}
        />
        <MenuBtn
            name='HTML'
            onPress={onPress}
        />
        <MenuBtn
            name='印刷'
            onPress={onPress}
        />
        <MenuBtn
            name='バックアップ'
            onPress={onPress}
        />
    </View>
    )
}

↑コードはこんな感じになりました。
今はonPressに仮の関数を渡していますが、後でエクスポート用の関数に変えます!

フォルダーメニュー

picture 11
↑フォルダーメニューを作成します。

ボタンを押すと開くのは設定メニューと同じですが、ファイルを選択した時などの挙動が変わるので、ちょっと難しいです。

picture 1

↑のようになりました。

// Folder.js
export default function Folder(params) {
    const { theme } = useTheme();
    const {
        setIsMenuOpen
    } = useContext(ContextObject)

    const [isTypeSelectMenuOpen, setTypeSelectMenuOpen]=useState(false)

    const styles = {
        view:{
            position: 'relative',
            zIndex:10
        },
        plusIconContainer: {
            position: 'absolute',
            top: -10,
            left: 0,
            zIndex:100,
        },
        plusIcon:{
            fontSize: 40
        }
    }

    function onPressPlusIcon() {
        { isTypeSelectMenuOpen ? setTypeSelectMenuOpen(false):setTypeSelectMenuOpen(true)}
    }

    return (
        <View style={styles.view}>
            <Icon
                name='add-circle'
                color={theme.PlusBtn.iconColor}
                containerStyle={styles.plusIconContainer}
                iconStyle={styles.plusIcon}
                onPress={onPressPlusIcon}
            />
            {isTypeSelectMenuOpen ? <TypeSelectMenu /> : <View/>}
            <MenuTitle>プロジェクト</MenuTitle>
            <Project
                project={{
                    projectName:'test',
                    fileList:['test','test2']
                }}
            />
        </View>
    )
}
// Folder.js
function Project(props) {
    const { theme } = useTheme();
    const [isOnonPressMenuBtn, setOnonPressMenuBtn] = useState(false)
    const projects = props.project
    const projectName = projects.projectName
    const fileList = projects.fileList

    const styles={
        nodata:{
            color: '#FFFFFF',
            marginTop: 30,
        },

    }

    function onPressMenuBtn(params) {
        { isOnonPressMenuBtn ? setOnonPressMenuBtn(false) : setOnonPressMenuBtn(true)}
    }


    return(
        <View>

            {!projectName ? <Text style={styles.nodata}>+ボタンから新規作成</Text>:
                <MenuBtn
                    name={projectName}
                    iconName={isOnonPressMenuBtn ?'folder-open':'folder'}
                    onPress={onPressMenuBtn}
                />
            }
            {isOnonPressMenuBtn?
                (
                    !fileList ? <Text style={styles.nodata}>+ボタンから新規作成</Text> :
                        (fileList.map(e=>{
                            return(
                                <MenuBtnChild
                                    name={e}
                                    iconName='text-snippet'
                                />
                            )
                        }))

                ):
            <View/>}

        </View>
    )
}

↑コードはコチラ
ファイルシステムを使って情報を取得するので、データはダミーです!

フォルダー・ファイル追加メニュー

picture 12
↑このように、新規ディレクトリ・ファイルを作成できるようにします。
とりあえず、ボタンを作成しておきます。

picture 3
↑このようになりました。

// Folder.js
function TypeSelectMenuBtn(props) {
    const { theme } = useTheme();

    const [isOnPress, setOnPress] = useState(false)

    console.log(isOnPress);
    const styles = {
        view: {
            backgroundColor: isOnPress ? theme.typeSelectMenu.BackgroundColor : theme.typeSelectMenu.onPress.BackgroundColor ,
            height: '50%',
            flexDirection: 'row',
            alignItems: 'center',
            borderTopLeftRadius: props.addType == 'addProject'? 20 : 0,
            borderTopEndRadius: props.addType == 'addProject' ? 20 : 0,
            borderTopRightRadius: props.addType == 'addProject' ? 20 : 0,
            borderTopStartRadius: props.addType == 'addProject' ? 20 : 0,
            borderBottomLeftRadius: props.addType == 'addProject' ? 0 : 20,
            borderBottomEndRadius: props.addType == 'addProject' ? 0 : 20,
            borderBottomRightRadius: props.addType == 'addProject' ? 0 : 20,
            borderBottomStartRadius: props.addType == 'addProject' ? 0 : 20,
            padding:20,
            // borderBottomWidth:3
        },
        icon:{
            color: isOnPress? theme.typeSelectMenu.iconColor : theme.typeSelectMenu.onPress.iconColor,
            marginRight: 10,
            fontSize: 30
        },
        text:{
            color: isOnPress? theme.typeSelectMenu.TextColor : theme.typeSelectMenu.onPress.TextColor,
            fontSize: 20
        }
    }

    function onPress() {
        props.onPress()
        { isOnPress ? setOnPress(false) : setOnPress(true) }
        console.log('onpress');
    }

    return (
            <Pressable style={styles.view} onPress={onPress}>
                <Icon
                name={props.iconName}
                iconStyle={styles.icon}
                />
            <Text style={styles.text}>{props.text}</Text>
            </Pressable>
    )

}

↑コードはこんな感じ。

プロジェクト・ファイル追加用のモーダル作成

デザインでは書いていなかったですが、プロジェクトやファイルを追加する際にファイル名を入力しないといけないことに気づきました。

画面いっぱいのモーダルを作って、そこにインプットエリアを追加することにします。

Modal.jsを作っていきます!

picture 4
↑できました!

import React, { useContext, useEffect, useState } from 'react';
import { Modal, TextInput, View, Pressable,Text} from 'react-native';
import { useTheme } from 'react-native-elements';
import { ContextObject } from '../../modules/context';

export function SetDataNameModal(props) {
    const { theme } = useTheme();

    const {
        isSetDataNameModalOpen,
        setSetDataNameModalOpen,
        whichSetDataNameModalOpen,
        newProjectName,
        setNewProjectName,
        newFileName,
        setNewFileName
    } = useContext(ContextObject)

    const styles = {
        centeredView: {
            flex: 1,
            justifyContent: "center",
            alignItems: "center",
            backgroundColor: 'rgba(255, 255, 255,0.5)',
            paddingBottom: props.keyboardPadding
        },
        modal: {
            flexDirection: 'row',
            justifyContent: "center",
            alignItems: "center",
            backgroundColor: theme.main.mainBackgroundColor,
            textAlign: 'center',
            width: '80%',
            padding:20,
            borderRadius: 20
        },
        textInput: {
            width: '80%',
            height: 50,
            backgroundColor: theme.textView.backgroundColor,
            color: theme.textView.textColor,
            margin: 20,
            paddingHorizontal: 20,
            borderRadius: 20,
            fontSize: 20,
        },
        btn: {
            height: 50,
            justifyContent: 'center',
            alignItems: 'center',
            backgroundColor: theme.main.secondBackgroundColor,
            borderRadius: 20,
            paddingHorizontal:20,
        },
        btnText: {
            color: theme.nav.iconColor,
            fontSize: 20,
        }
    }


    function onChangeText(text) {
        if (whichSetDataNameModalOpen == 'addProject'){
            setNewProjectName(text)
        } else if (whichSetDataNameModalOpen == 'addFile') {
            setNewFileName(text)
        }
    }

    function openModal(params) {
        setSetDataNameModalOpen(true)
    }

    function closeModal(params) {
        setSetDataNameModalOpen(false)
    }

    return (
        <Modal
            transparent={true}
            presentationStyle='overFullScreen'
            onRequestClose={closeModal}
            visible={isSetDataNameModalOpen}
            animationType='fade'
        >
            <Pressable style={styles.centeredView} onPress={closeModal}>
                <Pressable style={styles.modal} onPress={openModal}>
                <TextInput
                    style={styles.textInput}
                    onChangeText={text => { onChangeText(text)}}
                    placeholder={whichSetDataNameModalOpen == 'addProject'?'新規プロジェクト名':'新規ファイル名'}
                />
                    <Pressable
                        style={styles.btn}
                            onPress={closeModal}
                    >
                        <Text style={styles.btnText}>新規作成</Text>
                    </Pressable>
                </Pressable>
            </Pressable>
        </Modal>
    )
}

↑コードはこんな感じ

押したボタンによって動作が変わるように設定しています。

ここから、難関のファイルシステムが始まります(((o(゚▽゚)o)))
がんばります!ww

↓続き
【12】React Nativeでテキストエディタを作ってみる!【ファイルシステム編】【12】React Nativeでテキストエディタを作ってみる!【ファイルシステム編】

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です