View Basic Information Block
ViewBasicInformation is a key-value view block built on MUI with a header (title and optional action button), optional subheader.
Installationโ
- 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
What You Needโ
| Item | Why it matters |
|---|---|
labels.headerTitle | Main heading in the header (default ๅบๆฌๆ
ๅ ฑ) |
data | Field rows as a Record<string, ReactNode> (row keys become labels) or ReactNode[] for value-only rows |
labels.headerActionButton + headerAction (optional) | Outlined header button label (default ็ทจ้) and click handler |
labels.subheaderTitle (optional) | Secondary line under the header |
ViewBasicInformation does not own state - pass labels, data, and optional headerAction from your page. Object data keys become row labels; values can be strings or React nodes (multi-line text, links, custom layouts). Set labels.headerActionButton and headerAction to show the header action button.
Code Examplesโ
- Quick Start
- Labels
- Compound Components
- Block Override
function Example() { const data = { Name: ( <div> <div>Hanako Iwata</div> <div>IWATA HANAKO</div> </div> ), Address: '1-1-1 Higashiyoshinocho, Tokushima-shi, Tokushima 771-0801', Phone: '+81 90-1111-1111', }; return <ViewBasicInformation data={data} />; }
Customize header copy, action behavior, subheader text, and row content.
function Example() { const [lastAction, setLastAction] = React.useState(''); const labels = { headerTitle: 'Basic profile', subheaderTitle: 'Last updated May 15, 2026', headerActionButton: 'Edit information', }; const data = { Name: ( <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> ), Address: ( <Typography component="a" href="#account/profile" sx={{ color: 'primary.main', textDecoration: 'none' }}> 1-1-1 Higashiyoshinocho, Tokushima-shi, Tokushima </Typography> ), 'Contact number': '09011111111', }; return ( <Box> <ViewBasicInformation labels={labels} data={data} headerAction={() => setLastAction('Edit information')} /> {lastAction ? ( <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 1 }}> Last action: {lastAction} </Typography> ) : null} </Box> ); }
Put headerTitle, subheaderTitle, and headerActionButton on the root labels object (or on ViewBasicInformation.Header and ViewBasicInformation.HeaderActionButton in compound layouts). Omit labels to use the built-in defaults ๅบๆฌๆ
ๅ ฑ and ็ทจ้.
Compose ViewBasicInformation.Header, ViewBasicInformation.Subheader, and ViewBasicInformation.Item rows directly. Pass data on the root to override built-in defaults, and set labels / headerAction on child components for full layout control.
function Example() { const [lastAction, setLastAction] = React.useState(''); const data = { Name: ( <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> ), Address: '1-1-1 Higashiyoshinocho, Tokushima-shi, Tokushima 771-0801', Phone: '+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: 'Basic information', headerActionButton: 'Edit profile', }} headerAction={() => setLastAction('Edit profile')} 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: 'Member since November 2023 ยท Identity verified' }} 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' }}> Last action: {lastAction} </Typography> ) : null} </Box> ); }
Use function children with defaultBlocks and defaultBlockOrder to inject blocks, replace defaults, and control order.
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: 'Basic information', subheaderTitle: 'Last reviewed May 15, 2026 ยท Identity verified', headerActionButton: 'Edit profile', }; const data = { Name: ( <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> ), Address: '1-1-1 Higashiyoshinocho, Tokushima-shi, Tokushima 771-0801', Phone: 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('Edit profile')} 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' }}> Member 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 }}> Profile completeness </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', }} > Verified </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' }}> Address change under review </Typography> <Typography variant="caption" sx={{ display: 'block', mt: 0.5, color: 'text.secondary' }}> Your latest update is queued for verification and should be reflected within 24 hours. </Typography> </Box> <Box component="button" type="button" onClick={() => setNoticeVisible(false)} aria-label="Dismiss notification" 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('Edit profile')} 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 }} > Contact details </Typography> <Box component="button" type="button" onClick={() => setShowSensitive((value) => !value)} sx={{ ...actionButtonSx, py: 0.55, fontSize: 12, }} > {showSensitive ? 'Hide sensitive fields' : 'Reveal sensitive fields'} </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('Download profile PDF')} sx={actionButtonSx} > Download profile PDF </Box> <Box component="button" type="button" onClick={() => setLastAction('Open audit log')} sx={actionButtonSx} > Open audit log </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' }}> Last action: {lastAction} </Typography> ) : null} </Box> ); }
When to use block overrides
Default defaultBlockOrder is header, subheader, itemList, item, itemKey, itemValue. The default render uses itemList to map data; the separate item / itemKey / itemValue entries are for granular overrides. This example injects custom profileHero and profileActions blocks, prepends a dismissible verificationBanner, replaces header, subheader, and itemList, and defines a focused blockOrder (do not spread ...defaultBlockOrder, which can render empty default rows).
Important Propsโ
Core Propsโ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
data | Record<string, ReactNode> | ReactNode[] | No | { ๆฐๅ: <></>, ไฝๆ: <></>, ้ฃ็ตกๅ
้ป่ฉฑ็ชๅท: <></> } | Row content: object keys become field labels; arrays render value-only rows |
labels | { headerTitle?: string; headerActionButton?: string; subheaderTitle?: string } | No | { headerTitle: 'ๅบๆฌๆ
ๅ ฑ', headerActionButton: '็ทจ้' } | Header title, optional action button label, and optional subheader copy |
Content Propsโ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
labels.headerTitle | string | No | ๅบๆฌๆ
ๅ ฑ | Main heading in ViewBasicInformation.Header |
labels.subheaderTitle | string | No | undefined | Secondary line under the header (ViewBasicInformation.Subheader) |
labels.headerActionButton | string | No | ็ทจ้ | Label for the outlined header button; shown when labels.headerActionButton or headerAction is set (or passed on ViewBasicInformation.HeaderActionButton) |
headerAction | () => void | No | undefined | Invoked when the default header action button is clicked. |
Layout and Composition Propsโ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | No | undefined | Compound JSX children or function override returning blocks and blockOrder |
className | string | No | undefined | Class name on the root stack (nbb-view-basic-information) |
sx | SxProps | No | [{ margin: 'auto' }, ...sx] | MUI system styles for the root stack; default render prepends margin: 'auto' |
ViewBasicInformation inherits StackProps (except children). Default defaultBlockOrder: header, subheader, itemList, item, itemKey, itemValue.
Sub-component propsโ
Sub-components read from context and accept the same content keys as props to override locally.
| Sub-component | Main Props | Inherits | Built on |
|---|---|---|---|
ViewBasicInformation.Header | labels (headerTitle, headerActionButton), headerAction, children, className, sx | StackProps | Stack + Typography + optional 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 rows |
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") |
Default UI Blocksโ
| Block | Built on | Notes |
|---|---|---|
ViewBasicInformation (root) | Stack | Column layout with spacing={3}, margin: 'auto' (nbb-view-basic-information) |
header (ViewBasicInformation.Header) | Stack | Renders when labels.headerTitle, labels.headerActionButton, or custom children is set; includes ViewBasicInformation.HeaderActionButton when headerAction or labels.headerActionButton is set |
subheader (ViewBasicInformation.Subheader) | Stack | Renders when labels.subheaderTitle or custom children is set |
itemList (ViewBasicInformation.ItemList) | List | Maps data object entries or array values to key/value rows |
Default root render order: 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: 'Basic information',
headerActionButton: 'Edit',
};
const data: BasicInformationData = {
Name: 'Hanako Iwata',
Address: '1-1-1 Higashiyoshinocho, Tokushima-shi, Tokushima 771-0801',
Phone: '+81 90-1111-1111',
};
return (
<ViewBasicInformation
labels={labels}
data={data}
headerAction={() => {
/* navigate or open edit dialog */
}}
/>
);
}