今回の狙い
Web画面の機能といえば、メニューがあり、色々なサブ画面から成り立つ…
ですが、ちょっとした画面機能の場合、タブによる画面切替があると便利ですよね。
結構、曲者なのは、「ちょっとした」画面機能である事。
「ちょっと」なら、1プログラムで作っても、可読性が下がることはない…はず?
実際に構築してみると、あ~らあら… そんなこと多々ですよね。
という事で、今回は、見易いタブ画面のプログラム作成です。
実装
reactの初期状態を、極力、そのまま残して、簡単に作ってみました。
└── src
├── App.css (初期のcssを残してます)
├── App.js (ほぼ初期のままです)
├── index.css (初期のcssを残してます)
├── index.js (ほぼ初期のままです)
├── Page1.js (タブの制御処理です)
├── Tab01.js (タブの内容1)
└── Tab02.js (タブの内容2)
以下、順にプログラムを掲載してみます。
<index.js>は、解りやすい箇所だけ残した感じです。
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
indexに呼び出される<app.js>が以下です。
単純にタブを制御する<Page1.js>を組み込んでいるだけですね。
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Page1 from './Page1';
import { Box } from '@mui/material';
export default function App() {
return (
<Router>
<Box sx={{ display: 'flex' }}>
<Routes>
<Route path="/" element={<Page1 />} />
</Routes>
</Box>
</Router>
);
}
以下はタブの移動を管理する<Page1.js>になります。
import React, { useState } from 'react';
import { Box, Typography, Tabs, Tab } from '@mui/material';
import Tab01 from './Tab01';
import Tab02 from './Tab02';
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
export default function Page1() {
const [value, setValue] = useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<Box sx={{ p: 3, maxWidth: 1200, minWidth: 900, mx: 'auto' }}>
<Typography variant="h6">タブの制御</Typography>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={value} onChange={handleChange} aria-label="data catalog tabs">
<Tab label="文字入力例" {...a11yProps(0)} />
<Tab label="文字列の制御" {...a11yProps(1)} />
</Tabs>
</Box>
<CustomTabPanel value={value} index={0}><Tab01 /></CustomTabPanel>
<CustomTabPanel value={value} index={1}><Tab02 /></CustomTabPanel>
</Box>
);
}
// CustomTabPanelは共通で利用するため、このファイルに残します。
// プログラムが大きくなった場合は、別のファイルに切り出す等
function CustomTabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div role="tabpanel" hidden={value !== index} id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`} {...other}>
{value === index && <Box sx={{ p: 3 }}>{children}</Box>}
</div>
);
}
1つ目のタブ「文字入力例」<Tab01.js>のプログラムです。
import React, { useState } from 'react';
import { Box, TextField, Button, Typography } from '@mui/material';
export default function Tab01() {
const [inputText, setInputText] = useState('');
const [displayedText, setDisplayedText] = useState('');
const handleInputChange = (event) => {
setInputText(event.target.value);
};
const handleDisplay = () => {
setDisplayedText(inputText);
};
return (
<Box>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mb: 4 }}>
<TextField label="何か入力してください" variant="outlined" value={inputText} onChange={handleInputChange} fullWidth sx={{ maxWidth: 500 }} />
<Button variant="contained" onClick={handleDisplay} disabled={!inputText.trim()}>表示</Button>
</Box>
<Box>
<Typography variant="h6">入力されたテキスト:</Typography>
<Typography variant="body1">{displayedText}</Typography>
</Box>
</Box>
);
}
2つ目のタブ「文字列の制御」<Tab02.js>のプログラムです。
import React, { useState } from 'react';
import { Box, TextField, Button, Typography, List, ListItem, ListItemText, Divider } from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
export default function Tab02() {
const [itemSearchText, setItemSearchText] = useState('');
const [displayedKeywords, setDisplayedKeywords] = useState([]);
const [searchPerformed, setSearchPerformed] = useState(false);
const handleItemSearchChange = (event) => {
setItemSearchText(event.target.value);
};
const handleItemSearch = () => {
const keywords = itemSearchText.trim().split(/\s+/).filter(Boolean);
setDisplayedKeywords(keywords);
setSearchPerformed(true);
};
return (
<Box>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mb: 4 }}>
<TextField label="文字列 (スペース区切りで複数指定可)" variant="outlined" value={itemSearchText} onChange={handleItemSearchChange} fullWidth sx={{ maxWidth: 500 }} />
<Button variant="contained" onClick={handleItemSearch} startIcon={<SearchIcon />} disabled={!itemSearchText.trim()}>実行</Button>
</Box>
{searchPerformed && displayedKeywords.length > 0 && (
<List dense>
{displayedKeywords.map((keyword, index) => (
<React.Fragment key={index}>
<ListItem alignItems="flex-start">
<ListItemText primary={
<Typography component="span" variant="h6" color="text.primary">キーワード {index + 1}</Typography>
} secondary={
<Typography sx={{ display: 'block' }} component="span" variant="body1" color="text.secondary">{keyword}</Typography>
}
/>
</ListItem>
{index < displayedKeywords.length - 1 && <Divider component="li" />}
</React.Fragment>
))}
</List>
)}
</Box>
);
}
今回タブの内容は、それぞれ独立しているので、解りやすいですが、
本来、パーツ化して、パラメータ一杯で訳が分からなくなる… と。
職人さんであれば、かなり広範囲に理解されることでしょう。
きっと、ルール作りやノウハウも御持ちに違いない。
この程度であれば、Aiも簡単に自動生成してくれます。
以上、Aiに依頼する為の豆知識になる事を祈って、御疲れ様でした~。