ViewBasicInformationブロック
ViewBasicInformation は、MUI 上に構築されたキー・バリュー表示ブロックで、ヘッダー(タイトルと任意のアクションボタン)、任意のサブヘッダーを備えています。
インストール
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-view-basic-information-block
yarn add @nodeblocks/frontend-view-basic-information-block
pnpm add @nodeblocks/frontend-view-basic-information-block
bun add @nodeblocks/frontend-view-basic-information-block
必要なもの
| 項目 | 用途 |
|---|---|
labels.headerTitle | ヘッダーのメイン見出し(デフォルト 基本情報) |
data | フィールド行を Record<string, ReactNode>(行キーがラベルになる)または値のみの行用の ReactNode[] として渡す |
labels.headerActionButton + headerAction (任意) | アウトラインのヘッダーボタンのラベル(デフォルト 編集)とクリックハンドラー |
labels.subheaderTitle (任意) | ヘッダー下のセカンダリ行 |
ViewBasicInformation は状態を保持しません — ページから labels、data、任意の headerAction を渡してください。オブジェクト形式の data のキーが行ラベルになり、値は文字列または React ノード(複数行テキスト、リンク、カスタムレイアウト)にできます。labels.headerActionButton と headerAction を設定すると、ヘッダーアクションボタンが表示されます。
コード例
- クイックスタート
- ラベル
- 複合コンポーネント
- ブロックのオーバーライド
function Example() { const data = { 氏名: ( <div> <div>Hanako Iwata</div> <div>IWATA HANAKO</div> </div> ), 住所: '1-1-1 Higashiyoshinocho, Tokushima-shi, Tokushima 771-0801', 電話番号: '+81 90-1111-1111', }; return <ViewBasicInformation data={data} />; }
ヘッダーのコピー、アクションの動作、サブヘッダー、行の内容をカスタマイズします。
function Example() { const [lastAction, setLastAction] = React.useState(''); const labels = { headerTitle: '基本プロフィール', subheaderTitle: '最終更新: 2026年5月15日', headerActionButton: '情報を編集', }; const data = { 氏名: ( <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> <Typography variant="body2" sx={{ fontWeight: 600 }}> Hanako Iwata </Typography> <Typography variant="body2" color="text.secondary"> IWATA HANAKO </Typography> </Box> ), 住所: ( <Typography component="a" href="#account/profile" sx={{ color: 'primary.main', textDecoration: 'none' }}> 1-1-1 Higashiyoshinocho, Tokushima-shi, Tokushima </Typography> ), 連絡先電話番号: '09011111111', }; return ( <Box> <ViewBasicInformation labels={labels} data={data} headerAction={() => setLastAction('情報を編集')} /> {lastAction ? ( <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 1 }}> 最後のアクション: {lastAction} </Typography> ) : null} </Box> ); }
headerTitle、subheaderTitle、headerActionButton はルートの labels オブジェクト(または複合レイアウトでは ViewBasicInformation.Header / ViewBasicInformation.HeaderActionButton)に設定します。labels を省略すると、組み込みのデフォルト 基本情報 と 編集 が使われます。
ViewBasicInformation.Header、ViewBasicInformation.Subheader、ViewBasicInformation.Item 行を直接構成します。ルートに data を渡して組み込みデフォルトを上書きし、子コンポーネントに labels / headerAction を設定してレイアウトを完全に制御します。
function Example() { const [lastAction, setLastAction] = React.useState(''); const data = { 氏名: ( <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.25 }}> <Typography variant="body2" sx={{ fontWeight: 600 }}> Hanako Iwata </Typography> <Typography variant="caption" color="text.secondary"> IWATA HANAKO </Typography> </Box> ), 住所: '1-1-1 Higashiyoshinocho, Tokushima-shi, Tokushima 771-0801', 電話番号: '+81 90-1111-1111', }; const rows = Object.entries(data); return ( <Box sx={{ maxWidth: 560, mx: 'auto' }}> <ViewBasicInformation data={data} sx={{ p: 2.5, borderRadius: 3, border: '1px solid', borderColor: 'divider', bgcolor: 'background.paper', boxShadow: '0 10px 30px rgba(15, 23, 42, 0.08)', }} > <ViewBasicInformation.Header labels={{ headerTitle: '基本情報', headerActionButton: 'プロフィールを編集', }} headerAction={() => setLastAction('プロフィールを編集')} sx={{ px: 2, py: 1.75, borderRadius: 2, background: 'linear-gradient(135deg, #EEF2FF 0%, #F8FAFC 100%)', border: '1px solid', borderColor: 'primary.light', '& .MuiButton-outlined': { bgcolor: 'background.paper', fontWeight: 600, boxShadow: '0 2px 8px rgba(15, 23, 42, 0.08)', }, }} /> <ViewBasicInformation.Subheader labels={{ subheaderTitle: '2023年11月から会員 · 本人確認済み' }} sx={{ px: 2, mt: 1, color: 'text.secondary', fontWeight: 500, }} /> <Box component="dl" sx={{ m: 0, mt: 1.5, border: '1px solid', borderColor: 'divider', borderRadius: 2, overflow: 'hidden', }} > {rows.map(([term, description], index) => ( <ViewBasicInformation.Item key={term} sx={{ px: 2, py: 1.5, bgcolor: index % 2 === 0 ? 'grey.50' : 'background.paper', borderBottom: index < rows.length - 1 ? '1px solid' : 'none', borderColor: 'divider', }} > <ViewBasicInformation.Item.Key sx={{ minWidth: 132, color: 'primary.main', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.5, fontSize: 11, }} > {term} </ViewBasicInformation.Item.Key> <ViewBasicInformation.Item.Value sx={{ color: 'text.primary', fontWeight: 500, lineHeight: 1.6, }} > {description} </ViewBasicInformation.Item.Value> </ViewBasicInformation.Item> ))} </Box> </ViewBasicInformation> {lastAction ? ( <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 1.5, textAlign: 'center' }}> 最後のアクション: {lastAction} </Typography> ) : null} </Box> ); }
defaultBlocks と defaultBlockOrder を使う関数の children で、ブロックの注入、デフォルトの差し替え、順序の制御を行います。
function Example() { const [noticeVisible, setNoticeVisible] = React.useState(true); const [showSensitive, setShowSensitive] = React.useState(false); const [lastAction, setLastAction] = React.useState(''); const profileCompletion = 86; const labels = { headerTitle: '基本情報', subheaderTitle: '最終確認: 2026年5月15日 · 本人確認済み', headerActionButton: 'プロフィールを編集', }; const data = { 氏名: ( <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.25 }}> <Typography variant="body2" sx={{ fontWeight: 600 }}> Hanako Iwata </Typography> <Typography variant="caption" color="text.secondary"> IWATA HANAKO </Typography> </Box> ), 住所: '1-1-1 Higashiyoshinocho, Tokushima-shi, Tokushima 771-0801', 電話番号: showSensitive ? '+81 90-1111-1111' : '••• ••• ••••', }; const actionButtonSx = { px: 1.5, py: 0.85, borderRadius: 1.5, border: '1px solid', borderColor: 'divider', bgcolor: 'background.paper', color: 'text.primary', cursor: 'pointer', fontSize: 13, fontWeight: 600, transition: 'all 0.2s ease', '&:hover': { borderColor: 'primary.main', color: 'primary.main', boxShadow: '0 4px 14px rgba(79, 70, 229, 0.15)', }, }; return ( <Box sx={{ maxWidth: 640, mx: 'auto' }}> <ViewBasicInformation labels={labels} data={data} headerAction={() => setLastAction('プロフィールを編集')} sx={{ p: { xs: 2, sm: 2.5 }, borderRadius: 3, border: '1px solid', borderColor: 'divider', bgcolor: 'background.paper', boxShadow: '0 16px 40px rgba(15, 23, 42, 0.1)', }} > {({ defaultBlocks }) => ({ blocks: { ...defaultBlocks, profileHero: ( <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, px: 2.5, py: 2, mb: 1.5, borderRadius: 2.5, background: 'linear-gradient(135deg, #312E81 0%, #4F46E5 55%, #6366F1 100%)', color: 'common.white', boxShadow: '0 12px 32px rgba(79, 70, 229, 0.35)', }} > <Box sx={{ width: 56, height: 56, borderRadius: '50%', display: 'grid', placeItems: 'center', bgcolor: 'rgba(255,255,255,0.18)', border: '2px solid rgba(255,255,255,0.45)', }} > <PersonOutlined sx={{ fontSize: 30 }} /> </Box> <Box sx={{ flex: 1, minWidth: 0 }}> <Typography variant="subtitle1" sx={{ fontWeight: 700, color: 'inherit' }}> Hanako Iwata </Typography> <Typography variant="caption" sx={{ opacity: 0.88, display: 'block' }}> 会員 ID · NB-20481 </Typography> <Box sx={{ mt: 1.25 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}> <Typography variant="caption" sx={{ opacity: 0.9 }}> プロフィール完成度 </Typography> <Typography variant="caption" sx={{ fontWeight: 700 }}> {profileCompletion}% </Typography> </Box> <Box sx={{ height: 6, borderRadius: 99, bgcolor: 'rgba(255,255,255,0.25)', overflow: 'hidden' }}> <Box sx={{ width: `${profileCompletion}%`, height: '100%', borderRadius: 99, bgcolor: 'common.white', }} /> </Box> </Box> </Box> <Box sx={{ px: 1.25, py: 0.5, borderRadius: 99, bgcolor: 'rgba(16, 185, 129, 0.22)', border: '1px solid rgba(16, 185, 129, 0.55)', fontSize: 11, fontWeight: 700, letterSpacing: 0.4, textTransform: 'uppercase', whiteSpace: 'nowrap', }} > 確認済み </Box> </Box> ), verificationBanner: noticeVisible ? ( <Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 2, px: 2, py: 1.5, mb: 1.5, borderRadius: 2, bgcolor: 'rgba(245, 158, 11, 0.12)', border: '1px solid', borderColor: 'warning.main', }} > <Box> <Typography variant="body2" sx={{ fontWeight: 700, color: 'warning.dark' }}> 住所変更を審査中 </Typography> <Typography variant="caption" sx={{ display: 'block', mt: 0.5, color: 'text.secondary' }}> 最新の更新は確認待ちです。24時間以内に反映される予定です。 </Typography> </Box> <Box component="button" type="button" onClick={() => setNoticeVisible(false)} aria-label="通知を閉じる" sx={{ flexShrink: 0, border: '1px solid', borderColor: 'warning.main', bgcolor: 'background.paper', color: 'warning.dark', borderRadius: '50%', width: 28, height: 28, cursor: 'pointer', fontSize: 16, lineHeight: 1, }} > × </Box> </Box> ) : null, header: ( <ViewBasicInformation.Header labels={labels} headerAction={() => setLastAction('プロフィールを編集')} sx={{ px: 2, py: 1.5, borderRadius: 2, bgcolor: 'grey.50', border: '1px solid', borderColor: 'divider', '& .MuiButton-outlined': { bgcolor: 'background.paper', fontWeight: 600, borderColor: 'primary.main', color: 'primary.main', }, }} /> ), subheader: ( <ViewBasicInformation.Subheader labels={{ subheaderTitle: labels.subheaderTitle }} sx={{ px: 2, mt: 0.75, color: 'text.secondary', fontWeight: 500, }} /> ), itemList: ( <Box sx={{ mt: 1.25 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1, mb: 1, px: 0.5, }} > <Typography variant="overline" sx={{ color: 'text.secondary', fontWeight: 700, letterSpacing: 1.1, lineHeight: 1.2 }} > 連絡先詳細 </Typography> <Box component="button" type="button" onClick={() => setShowSensitive((value) => !value)} sx={{ ...actionButtonSx, py: 0.55, fontSize: 12, }} > {showSensitive ? '機微フィールドを非表示' : '機微フィールドを表示'} </Box> </Box> <ViewBasicInformation.ItemList data={data} sx={{ border: '1px solid', borderColor: 'divider', borderRadius: 2, overflow: 'hidden', '& .MuiListItem-root': { px: 2, py: 1.5, transition: 'background-color 0.2s ease', '&:hover': { bgcolor: 'action.hover' }, }, '& .MuiListItem-root:nth-of-type(even)': { bgcolor: 'grey.50', }, '& .MuiTypography-subtitle1': { minWidth: 132, color: 'primary.main', fontWeight: 700, textTransform: 'uppercase', fontSize: 11, letterSpacing: 0.5, }, '& .MuiTypography-body2': { fontWeight: 500, }, }} /> </Box> ), profileActions: ( <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mt: 2, pt: 2, borderTop: '1px dashed', borderColor: 'divider', }} > <Box component="button" type="button" onClick={() => setLastAction('プロフィール PDF をダウンロード')} sx={actionButtonSx} > プロフィール PDF をダウンロード </Box> <Box component="button" type="button" onClick={() => setLastAction('監査ログを開く')} sx={actionButtonSx} > 監査ログを開く </Box> </Box> ), }, blockOrder: [ 'profileHero', ...(noticeVisible ? ['verificationBanner'] : []), 'header', 'subheader', 'itemList', 'profileActions', ], })} </ViewBasicInformation> {lastAction ? ( <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 1.5, textAlign: 'center' }}> 最後のアクション: {lastAction} </Typography> ) : null} </Box> ); }
ブロックのオーバーライドを使うタイミング
デフォルトの defaultBlockOrder は header、subheader、itemList、item、itemKey、itemValue です。デフォルトのレンダーは itemList で data をマッピングします。個別の item / itemKey / itemValue エントリは、きめ細かなオーバーライド用です。この例では、カスタムの profileHero と profileActions ブロックを注入し、閉じられる verificationBanner を先頭に追加し、header、subheader、itemList を差し替え、焦点を絞った blockOrder を定義しています(...defaultBlockOrder を展開しないでください。空のデフォルト行がレンダリングされる可能性があります)。
主要プロパティ
コアプロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
data | Record<string, ReactNode> | ReactNode[] | いいえ | { 氏名: <></>, 住所: <></>, 連絡先電話番号: <></> } | 行コンテンツ: オブジェクトのキーがフィールドラベルになる; 配列は値のみの行をレンダリング |
labels | { headerTitle?: string; headerActionButton?: string; subheaderTitle?: string } | いいえ | { headerTitle: '基本情報', headerActionButton: '編集' } | ヘッダータイトル、任意のアクションボタンラベル、任意のサブヘッダーのテキスト |
コンテンツプロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
labels.headerTitle | string | いいえ | 基本情報 | ViewBasicInformation.Header のメイン見出し |
labels.subheaderTitle | string | いいえ | undefined | ヘッダー下のセカンダリ行(ViewBasicInformation.Subheader) |
labels.headerActionButton | string | いいえ | 編集 | アウトラインのヘッダーボタンのラベル; labels.headerActionButton または headerAction が設定されている場合(または ViewBasicInformation.HeaderActionButton に渡されている場合)に表示 |
headerAction | () => void | いいえ | undefined | デフォルトのヘッダーアクションボタンがクリックされたときに呼び出される |
レイアウトと構成プロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | いいえ | undefined | 複合 JSX の子、または blocks と blockOrder を返す関数オーバーライド |
className | string | いいえ | undefined | ルートスタックのクラス名(nbb-view-basic-information) |
sx | SxProps | いいえ | [{ margin: 'auto' }, ...sx] | ルートスタック用の MUI システムスタイル; デフォルトレンダーは margin: 'auto' を先頭に追加 |
ViewBasicInformation は StackProps(children を除く)を継承します。デフォルトの defaultBlockOrder: header、subheader、itemList、item、itemKey、itemValue。
サブコンポーネントのプロパティ
サブコンポーネントはコンテキストから読み取り、同じコンテンツキーをプロパティとして渡すとローカルで上書きできます。
| サブコンポーネント | 主要プロパティ | 継承 | ベース |
|---|---|---|---|
ViewBasicInformation.Header | labels(headerTitle、headerActionButton)、headerAction、children、className、sx | StackProps | Stack + Typography + 任意の ViewBasicInformation.HeaderActionButton |
ViewBasicInformation.HeaderActionButton | labels(headerActionButton)、headerAction、children、className、onClick | ButtonProps | Button(variant="outlined"、size="medium") |
ViewBasicInformation.Subheader | labels(subheaderTitle、headerTitle、headerActionButton)、children、className、sx | StackProps | Stack + Typography |
ViewBasicInformation.ItemList | data、children、className、sx | ListProps | List(component="dl")+ ViewBasicInformation.Item 行 |
ViewBasicInformation.Item | children、className、sx | ListItemProps | ListItem |
ViewBasicInformation.Item.Key | children、className、sx | TypographyProps | Typography(component="dt"、variant="subtitle1") |
ViewBasicInformation.Item.Value | children、className、sx | TypographyProps | Typography(component="dd"、variant="body2") |
デフォルト UI ブロック
| ブロック | ベース | 備考 |
|---|---|---|
ViewBasicInformation(ルート) | Stack | spacing={3}、margin: 'auto' の縦レイアウト(nbb-view-basic-information) |
header(ViewBasicInformation.Header) | Stack | labels.headerTitle、labels.headerActionButton、またはカスタム children が設定されている場合にレンダリング; headerAction または labels.headerActionButton が設定されている場合は ViewBasicInformation.HeaderActionButton を含む |
subheader(ViewBasicInformation.Subheader) | Stack | labels.subheaderTitle またはカスタム children が設定されている場合にレンダリング |
itemList(ViewBasicInformation.ItemList) | List | data オブジェクトのエントリまたは配列の値をキー/バリュー行にマッピング |
デフォルトのルートレンダリング順序: header → subheader → itemList。
TypeScript
import * as React from 'react';
import { ViewBasicInformation } from '@nodeblocks/frontend-view-basic-information-block';
import type { ReactNode } from 'react';
type BasicInformationLabels = {
headerTitle?: string;
headerActionButton?: string;
subheaderTitle?: string;
};
type BasicInformationData = Record<string, ReactNode> | ReactNode[];
export function BasicInformationDetailView() {
const labels: BasicInformationLabels = {
headerTitle: '基本情報',
headerActionButton: '編集',
};
const data: BasicInformationData = {
氏名: 'Hanako Iwata',
住所: '1-1-1 Higashiyoshinocho, Tokushima-shi, Tokushima 771-0801',
電話番号: '+81 90-1111-1111',
};
return (
<ViewBasicInformation
labels={labels}
data={data}
headerAction={() => {
/* 遷移または編集ダイアログを開く */
}}
/>
);
}