Skip to content

@msflib/react-components

Shared UI component library covering form building (FormBuilder), data tables, a chat UI kit (including Markdown message rendering and an "agent thinking" status pattern), drag-and-drop lists, tree views, Google auth buttons, resizable panes, skeleton loaders, an OTP input, and custom SVG icons. Install once and pull in only what you use via subpath imports.

Imports

Every component is available from the package root (@msflib/react-components), and also from its own subpath:

import { FormBuilder } from '@msflib/react-components';
// or
import FormBuilder from '@msflib/react-components/formbuilder';

Both resolve to the same component and both keep working — the root import is unchanged and not deprecated. The subpath import only pulls that component's own dependencies instead of every component's combined dependencies, which matters in bundler-based dev servers (e.g. Next.js webpack/Turbopack) that don't tree-shake in development: importing from the root there compiles the full dependency graph (MUI X Data Grid, @dnd-kit, etc.) on first load even if you only render one component. Vite-based dev servers (Storybook, most playgrounds) aren't affected either way, since they pre-bundle dependencies once at startup rather than per route.

Available subpaths: ./formbuilder, ./table, ./chat-ui, ./markdown, ./draggable, ./tree-view, ./google-auth, ./resizable-pane, ./skeleton, ./otp-input, ./customsvg.

Component Breakdown

FormBuilder — ./formbuilder

Renders a form from a declarative array of field definitions instead of hand-wiring each input.

import FormBuilder from '@msflib/react-components/formbuilder';

<FormBuilder
  elements={[
    { id: 'email', name: 'email', label: 'Email', dType: 'string', eType: 'email' },
    { id: 'password', name: 'password', label: 'Password', dType: 'string', eType: 'password' },
  ]}
  formData={formData}
  setFormData={setFormData}
  onSubmit={(data, reset) => { /* submit, then optionally reset() */ }}
/>

FormBuilderProps

Prop Type Required Description
elements FormElement[] Yes Field definitions — see below.
onSubmit (data: any, reset: () => void) => void Yes Called with the current form data; call reset() to clear the form.
formData any Yes Controlled form state object.
setFormData (data: any) => void No Setter for formData; FormBuilder calls it on every field change.
resetFormOnSubmit boolean No Auto-reset after a successful submit.
loadingState boolean No Disables inputs / shows a loading state on submit buttons.
layout React.ComponentType<LayoutProps> No Supply your own layout component instead of the default flex-wrap grid. Receives { FormField, elements, formData, setFormData, isMobile, loading }.

FormElement (each entry in elements)

Field Type Required Description
id string Yes Unique identifier for the element.
label ReactNode Yes Field label.
name string Yes Form data key.
dType string Yes Data type (string, number, etc.) — drives the fallback renderer when eType isn't set.
eType string No Input renderer: text, password, email, number, date, datetime, textarea, select, file, checkbox, radio, autocomplete, searchlistmenu, taginput, switch, upload, otp, button.
placeholder string No Input placeholder text.
validation any No react-hook-form validation rules.
width number No Field width as a percentage.
helperText string No Helper text below the field.
disabled boolean No Disables the field.
mData object No Field-specific config — see below.

FormElement.mData

Field Type Description
disabled / required / readOnly boolean Field state flags.
align / variant / color string MUI-style visual options; color is a MUI palette key.
rows number Textarea row count.
accept string File input accept filter.
select / options boolean / OptionItem[] Renders as a select with the given { value, label } options.
width number Overrides the top-level width.
preview_upload boolean Shows an image preview for upload fields.
isCustomLabel / label_style boolean / CSSProperties Use a custom label style instead of the default MUI label.
inputProps any Passed straight through to the underlying MUI input.
href string For link-style button elements.
startIcon / endIcon IconType { icons: Record<string, ReactNode>, clickBehavior?: 'none' \| 'toggle' \| (() => void) } — icons rendered at the start/end of the input. endIcon defaults to a password show/hide toggle for password fields if omitted.
searchPlaceholder string Placeholder for searchlistmenu fields.
sx SxProps MUI sx object — see Styling below.
onClick (event) => void Click handler, mainly for button elements.
otpLength / otpType / mask number / 'numeric' \| 'alphanumeric' / boolean Only for eType: 'otp' — see OTP Input.

Styling — per-field visual customisation goes through mData.sx (a plain MUI sx object), not by forking the component. From this repo's playground (apps/playground/styles/form.styles.ts):

export const baseMData = {
  sx: {
    width: '100%',
    '& .MuiInputBase-root': {
      borderRadius: '12px',
      '& .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(0, 0, 0, 0.1)' },
      '&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: '#4f46e5', borderWidth: '2px' },
    },
  },
  isCustomLabel: true,
  label_style: { marginBottom: '6px' },
};

export const buttonStyle = (color = '#4f46e5') => ({
  borderRadius: '12px',
  backgroundColor: color,
  '&:hover': { backgroundColor: '#4338ca' },
});

Spread it into each field's mData, and into a button element's sx:

{ id: 'email', name: 'email', label: 'Email', dType: 'string', eType: 'email', mData: { ...baseMData } },
{ id: 'submit', name: 'submit', label: 'Sign in', dType: 'string', eType: 'button', mData: { sx: buttonStyle() } },

See the full file for dark-mode overrides via @media (prefers-color-scheme: dark) and a .dark & selector for class-based theming.

Table — ./table

A MUI X Data Grid wrapper (TableWidget) with built-in search, row actions menu, and optional drag-to-reorder.

import TableWidget from '@msflib/react-components/table';

<TableWidget
  rows={rows}
  columns={columns}
  enableSearch
  checkboxSelection
  onRowClick={(params) => console.log(params.row)}
/>
Prop Type Required Description
rows any[] Yes Standard MUI X Data Grid rows.
columns any[] Yes Standard MUI X Data Grid column definitions.
pageSize number No Rows per page. Defaults to 5.
pageSizeOptions number[] No Options shown in the page-size selector.
loading boolean No Shows the grid's loading state.
onRowClick (params: any) => void No Fired when a row is clicked.
onRowSelectionModelChange (newSelection: any) => void No Fired when the selection changes.
checkboxSelection boolean No Adds a selection checkbox column.
enableSearch boolean No Shows a built-in search box that filters rows.
tableTitle string No Title shown next to the search box.
menuItem boolean No Adds a per-row "..." actions column. Must be true for menuItems/handleMenuClick to have any effect.
menuItems MenuActionItem[] No { key, label } options shown in the per-row actions menu.
handleMenuClick (item: MenuActionItem, selectedRow: any) => void No Called when a menu action is chosen.
draggable boolean No Enables drag-to-reorder rows.
onRowsReorder (rows: any[]) => void No Called with the new row order after a drag.
autoHeight boolean No Grid height fits its content instead of filling the container.
styles StyleProps No Per-part sx overrides: header, body, cell, row, rowHover, headers, root.

Styling — pass a styles object targeting each part of the grid. From the playground (apps/playground/app/(public)/docs/tablewidget/table.styles.ts):

export const adaptiveTableStyles = {
  headers: { backgroundColor: '#f8fafc', color: '#111111' },
  row: { color: '#111111' },
  rowHover: { backgroundColor: '#f5f5f5' },
  root: {
    borderRadius: '14px',
    border: '1px solid #e5e7eb',
    '& .MuiDataGrid-cell': { borderBottomColor: '#f1f5f9' },
  },
};
<TableWidget rows={rows} columns={columns} styles={adaptiveTableStyles} />

See the full file for the paired dark-mode values (.dark & selectors) for every part.

ChatBox — ./chat-ui

A full chat UI: message list, input box, suggestions, feedback actions, file upload, and an auto-scroll-to-bottom behavior.

import ChatBox from '@msflib/react-components/chat-ui';

<ChatBox
  messages={messages}
  value={draft}
  onChange={setDraft}
  onSend={(text) => sendMessage(text)}
/>
Prop Type Required Description
messages ChatMessage[] Yes { id, role, content, avatar?, createdAt?, suggestions?, file?, files? } — content is rendered as Markdown by default when it's a string.
value string No Controlled input box value.
onChange (value: string) => void No Input box change handler.
placeholder string No Input box placeholder.
disabled boolean No Disables the input box and send button.
roleMap Record<string, 'user' \| 'assistant' \| 'system'> No Maps your own role strings to the three layout roles if they don't match directly.
onSend (value: string) => void No Called when the user sends a message.
onUpload () => void No Called when the attach-file button is clicked.
onSuggestionClick (suggestion: ChatSuggestion) => void No Called when a suggestion chip is clicked.
onFeedback (message: ChatMessage, action: ChatFeedbackAction) => void No Called when a feedback button is clicked.
className / bodyClassName / messageClassName string No Class names for the outer container, message list, and individual bubbles.
textFieldProps TextFieldProps No Props passed straight through to the underlying MUI TextField.
textFieldSx SxProps No sx override for the input box.
autoScroll boolean No Auto-scrolls to the latest message.
scrollBehavior ScrollBehavior No 'auto' \| 'smooth', passed to the scroll call.
scrollThreshold number No Distance (px) from the bottom within which auto-scroll still kicks in.
allowFeedback boolean No Shows per-message feedback buttons.
feedbackActions ChatFeedbackAction[] No { name, icon, label? } — the feedback buttons to show.
displayAvatar boolean No Shows each message's avatar.
icons ChatBoxIcons No { sendIcon?, attachmentIcon? } overrides.
messageMaxWidth CSSProperties['maxWidth'] No Max width of a message column (avatar + bubble + actions). Defaults to '70%'.
disableMarkdown boolean No Renders string content as plain text instead of Markdown.
styles object No Per-part CSSProperties: root, body, footer, bubble, assistantBubble, userBubble, systemBubble, sendButton, uploadButton, feedbackButton, suggestionButton, avatar, dateSeparator, timestamp.
renderMessage (message: ChatMessage, role: ChatComponentRole) => ReactNode No Fully override how a single message renders (see renderChatMessage.tsx example in the playground app).
renderMessageCard (message: ChatMessage, role: ChatComponentRole) => ReactNode No Override just the card/bubble wrapper, keeping default content layout.
renderHeader ReactNode \| (() => ReactNode) No Custom header above the message list.
renderFooterActions () => ReactNode No Custom actions rendered in the footer, alongside the input box.

Styling — two levels: the granular styles object (per-part CSSProperties — bubble, userBubble, assistantBubble, sendButton, avatar, etc.) for most visual tweaks, and className/bodyClassName/messageClassName/textFieldSx for container-level and input-box styling. messageMaxWidth controls bubble width directly. For full control over a bubble's markup (not just its style), use renderMessage.

<ChatBox
  messages={messages}
  bodyClassName="gap-4 px-6"
  messageMaxWidth="80%"
  textFieldSx={{ borderRadius: '12px' }}
  styles={{
    userBubble: { backgroundColor: '#4f46e5', color: '#fff' },
    assistantBubble: { backgroundColor: '#f4f4f5' },
  }}
/>

Markdown — ./markdown

The MarkdownMessage renderer ChatBox uses internally for string message content — use it standalone if you're building your own chat layout instead of using ChatBox.

import { MarkdownMessage } from '@msflib/react-components/markdown';

<MarkdownMessage content={responseText} />

Handles code blocks with syntax highlighting and a copy button, tables (with a copy-as-CSV affordance), and math delimiter normalization.

Prop Type Required Description
content ReactNode \| MarkdownContentPart[] Yes The message content. Also accepts a LangChain-style content-parts array ([{ text: '...' }, ...]) — joined to plain text automatically.
disableMarkdown boolean No Renders content as plain text instead of Markdown.
disableGfm boolean No Disables GitHub-Flavored Markdown (tables, strikethrough, task lists).
enableMath boolean No Enables math delimiter rendering ($...$, $$...$$).
disableSyntaxHighlight boolean No Disables code-block syntax highlighting.
syntaxTheme Record<string, CSSProperties> No Highlighting theme for code blocks — pass the package's own oneLight/oneDark, or your own.
disableCopy boolean No Removes the copy button from code blocks and tables.
styles MarkdownMessageStyles No Per-element-type CSSProperties: root, paragraph, heading1/heading2/heading3, list, listItem, link, blockquote, table, tableHeadCell, tableCell, codeInline, codeBlock, codeBlockWrapper, image.
components Partial<Components> No Override individual react-markdown element renderers directly.

Styling — the styles object above covers every element type Markdown can render. For syntax highlighting specifically, use syntaxTheme.

import { MarkdownMessage, oneDark } from '@msflib/react-components/markdown';

<MarkdownMessage
  content={responseText}
  styles={{ link: { color: '#4f46e5' } }}
  syntaxTheme={oneDark}
/>

Draggable — ./draggable

A sortable list (built on @dnd-kit) for reordering an arbitrary array of items.

import DraggableList from '@msflib/react-components/draggable';

// Each item must have an `id: string` — it's used as the drag key.
<DraggableList
  items={items} // e.g. [{ id: '1', label: 'First' }, { id: '2', label: 'Second' }]
  onDragEnd={(reordered) => setItems(reordered)}
  renderItem={(item) => <div>{item.label}</div>}
/>
Prop Type Required Description
items T[] (each item extends { id: string }) Yes The list to render and reorder.
onDragEnd (reorderedItems: T[]) => void Yes Called with the new order after a drag completes.
renderItem (item: T) => ReactNode Yes Renders each item's content.
itemClassName string No Class name applied to each draggable row wrapper.

Styling — itemClassName styles each draggable row wrapper; style the item's own content however you like inside renderItem.

TreeView — ./tree-view

A file/folder-style tree, with optional file-type icons, multi-select, and controlled or uncontrolled expansion/selection.

import { TreeView } from '@msflib/react-components/tree-view';

<TreeView
  items={tree}
  getItemId={(item) => item.id}
  getItemLabel={(item) => item.name}
  getItemChildren={(item) => item.children}
  showIcons
/>
Prop Type Required Description
items TItem[] Yes Root-level items. Defaults to { id, label, children?, expandable?, disabled?, extension?, icon? } if you don't supply your own TItem shape.
getItemId / getItemLabel / getItemChildren functions No Accessors for a custom TItem shape. Default to reading id/label/children directly.
isItemExpandable / isItemDisabled (item: TItem) => boolean No Override expandability/disabled state per item.
getItemIcon / getItemExtension functions No Per-item icon override, and the file extension used to look up fileIconMap.
showIcons boolean No Shows folder/file-type icons beside each item. Off by default.
folderIcon ReactNode No Icon for expandable items when showIcons is true, unless getItemIcon returns one for that item.
fileIconMap Record<string, ReactNode> No Extension (no leading dot) → icon, merged over the built-in default map. Only consulted for non-expandable items.
itemGap number \| string No Vertical spacing between sibling items at every depth. Defaults to 0.
indentSize 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| number No Horizontal indent per depth level (xs=12, sm=16, md=20, lg=28, xl=36, or a raw px number). Defaults to 'md'.
icons TreeViewIcons No { expandIcon?, collapseIcon?, leafIcon?, loadingIcon? }.
styles TreeViewStyles No Per-part CSSProperties: root, group, item, itemSelected, itemDisabled, itemFocused, icon, typeIcon, label, endAdornment.
className / itemClassName / groupClassName / labelClassName / endAdornmentClassName string No Class names for the root, each item, each children group, each label, and each end adornment.
expandedItems / defaultExpandedItems / onExpandedItemsChange — No Controlled or uncontrolled expansion state.
onItemExpansionToggle (event, itemId, isExpanded) => void No Fired when a single item's expansion is toggled.
multiSelect boolean No Allow selecting more than one item.
selectedItems / defaultSelectedItems / onSelectedItemsChange TreeViewSelection (string \| string[] \| null) No Controlled or uncontrolled selection state.
onItemClick / onItemFocus (event, itemId) => void No Click/focus handlers.
isItemLoading (itemId: string) => boolean No Shows icons.loadingIcon for an item (e.g. lazy-loaded children).
renderLabel (meta: TreeViewItemMeta<TItem>) => ReactNode No Fully override a label's markup.
renderItem (meta, defaultNode: ReactNode) => ReactNode No Fully override an item's markup, with the default render available to wrap.
renderItemEndAdornment (meta: TreeViewItemMeta<TItem>) => ReactNode No Right-aligned content per item (e.g. a lock icon, a VCS status letter).
aria-label / aria-labelledby string No Accessibility labeling for the root element.

Styling — styles: TreeViewStyles for per-part overrides, plus className/itemClassName/groupClassName/labelClassName for targeted CSS classes, and itemGap/indentSize for spacing. Use renderLabel/renderItem to fully customize a row's markup instead of fighting the defaults with CSS.

Google Auth — ./google-auth

A "Sign in with Google" button plus the redirect/callback handling it needs.

import { GoogleAuthButton, GoogleAuthHandler } from '@msflib/react-components/google-auth';

<GoogleAuthButton apiBaseUrl="https://api.example.com" />

// On your redirect/callback route:
<GoogleAuthHandler
  apiBaseUrl="https://api.example.com"
  onSuccess={(data) => { /* store token, redirect, etc. */ }}
/>

GoogleAuthButtonProps (extends MUI's ButtonProps — sx, variant, color, fullWidth, etc. all work directly)

Prop Type Required Description
apiBaseUrl string Yes Base URL the login redirect is built against.
label string No Button text.
redirectUrl string No Where Google redirects back to after sign-in.
onRedirect () => void No Called right before the redirect to Google happens.
googleLoginPath string No API path that kicks off the Google login flow.
as React.ElementType No Render as a different element/component instead of MUI's Button.
iconSize number No Size of the Google icon.

GoogleAuthHandlerProps (place on your redirect/callback route)

Prop Type Required Description
apiBaseUrl string Yes Base URL used to complete the callback exchange.
onSuccess (data: any) => void Yes Called with the authenticated session data.
redirectUrl string No Where to send the user after a successful callback.
onError (error: any) => void No Called if the callback exchange fails.
modalComponent ReactNode No Custom "signing you in..." UI shown while the callback is processed.

There's also a useGoogleCallback(options) hook (UseGoogleCallbackOptions: apiBaseUrl, redirectUrl?, callbackEndpoint?, onSuccess, onError?) if you want to handle the callback yourself instead of using GoogleAuthHandler.

Styling — GoogleAuthButtonProps extends MUI's ButtonProps directly, so sx, variant, color, fullWidth, etc. all work exactly as they do on a plain MUI Button.

<GoogleAuthButton apiBaseUrl="https://api.example.com" variant="outlined" sx={{ borderRadius: '12px' }} />

Resizable Pane — ./resizable-pane

A horizontal or vertical split pane with draggable (and keyboard-operable) dividers.

import { ResizablePane } from '@msflib/react-components/resizable-pane';

<ResizablePane
  direction="horizontal"
  panes={[
    { initialSize: '30%', minSize: 200, render: () => <Sidebar /> },
    { render: () => <MainContent /> },
  ]}
/>

ResizablePaneProps

Prop Type Required Description
panes PaneConfig[] Yes The panes to render, in order.
direction 'horizontal' \| 'vertical' No Split direction. Defaults to 'horizontal'.
style CSSProperties No Styles the container.
paneDividerStyle CSSProperties No Styles each drag handle between panes.
className string No Class name on the root.

PaneConfig (each entry in panes)

Field Type Description
render () => ReactNode The pane's content.
initialSize number \| \${number}%`| Starting size (px or percentage). Defaults to200`.
minSize / maxSize number \| \${number}%`| Resize bounds.minSizedefaults to50`. Dividers are also keyboard-operable (arrow keys move by a fixed step within these bounds).

Styling — style sets the container's CSS, paneDividerStyle styles the drag handles between panes, and className applies to the root.

<ResizablePane
  panes={panes}
  style={{ borderRadius: '12px', overflow: 'hidden' }}
  paneDividerStyle={{ backgroundColor: '#e5e7eb', width: '2px' }}
/>

Skeleton — ./skeleton

Loading-state placeholders: AppSkeleton for a generic block, SkeletonLoaderWrapper to swap a layout component for its skeleton version while loading is true, and SkeletonCardLayout for a ready-made card skeleton.

import { SkeletonLoaderWrapper } from '@msflib/react-components/skeleton';

<SkeletonLoaderWrapper loading={isLoading} layout={CardLayout}>
  <Card data={data} />
</SkeletonLoaderWrapper>

SkeletonLoaderWrapperProps

Prop Type Required Description
loading boolean Yes Shows the skeleton (via layout) while true; renders children once false.
layout React.ComponentType<{ className?: string }> Yes The skeleton shape to render while loading — typically a layout matching your real content's structure.
children ReactNode Yes The real content, rendered once loading is false.
skeletonLength number No Number of skeleton items to repeat (for list-style layouts).
className string No Class name on the wrapper.
useSwiperSlide / slideClassName boolean / string No Wrap each skeleton item in a Swiper slide, for carousel-style loading states.

AppSkeletonProps — a generic skeleton block (also exposes AppSkeleton.Avatar as a sub-component)

Prop Type Description
children ReactNode Content rendered inside the block.
className string Class name on the block.
dataTestId string data-testid for testing.

TextSkeletonProps (extends MUI's SkeletonProps)

Prop Type Description
lines number Number of skeleton text lines to render.

SkeletonLayoutProps — used by SkeletonCardLayout and any custom layout you write

Prop Type Description
className string Class name on the layout root.

Styling — className on AppSkeleton/SkeletonLoaderWrapper targets the wrapper; the skeleton's actual shape comes from the layout component you pass in (it renders that layout's structure in a skeleton state), so style it by styling your layout component.

OTP Input — ./otp-input

A segmented one-time-code input with auto-advance, backspace/arrow-key navigation, and paste support.

import OtpInput from '@msflib/react-components/otp-input';

<OtpInput length={6} onComplete={(code) => verifyOtp(code)} />
Prop Type Description
length number Number of boxes. Defaults to 6.
value / defaultValue string Controlled or uncontrolled value.
onChange (value: string) => void Fires on every change with the joined value.
onComplete (value: string) => void Fires once every box is filled.
type 'numeric' \| 'alphanumeric' Restricts allowed characters per box.
mask boolean Renders each box as a password field.
disabled boolean Disables every box.
error boolean Error state styling.
autoFocus boolean Focuses the first box on mount.
name string Renders a hidden input with the joined value, for plain form submission.
gap number Spacing between boxes.
boxSize number Size of each box.
className / style string / CSSProperties Styling for the wrapper.
ariaLabel string Accessibility label.

Styling — className/style target the wrapper; gap and boxSize control spacing and box size directly rather than needing CSS overrides.

<OtpInput length={6} boxSize={48} gap={12} onComplete={verifyOtp} />

Custom SVG — ./customsvg

Renders an SVG file as an inline, stylable icon (so fill/stroke via props or CSS actually work, unlike an <img> tag).

import CustomSvg from '@msflib/react-components/customsvg';

<CustomSvg src="/icons/logo.svg" fill="currentColor" />
Prop Type Required Description
src string Yes URL/path to the SVG file. Fetched and inlined (not rendered as an <img>).
className string No Class name on the wrapping <span>.
fill string No Overrides the SVG's fill color.
stroke string No Overrides the SVG's stroke color.

Styling — className for layout/sizing via CSS, plus fill/stroke props to recolor the icon directly (useful with currentColor to inherit the surrounding text color).