@bayonai/ui
v0.3.0
Published
Accessible React and Material UI primitives extracted from Bayon applications.
Maintainers
Readme
@bayonai/ui
React 19 / Material UI 9 primitives for Bayon applications. Thunderlist is the first consumer. The package ships tree-shakeable ESM and CommonJS artifacts, TypeScript declarations, and explicit client-entry directives for all public subpaths. It is versioned independently from the host application.
Consumers must provide the peer dependencies declared in package.json: React
and React DOM 19.2, Material UI 9, Material UI icons, and Emotion 11. React
framework consumers can import the documented subpaths directly; no root export
is intentionally provided. The package supports Node 20+ for build and release
tooling and browsers supported by the installed React/MUI peer versions.
Actions
Import LoadingIconButton, ActionMenu, and StickyActionFooter from @bayonai/ui/actions.
LoadingIconButtonpreserves a MUI icon button's footprint while replacing its child with a labelled spinner and disabling repeat interaction. Consumers provide both the ordinaryaria-labeland the in-flightloadingLabel; tooltips and request ownership remain with the host component.ActionMenuowns the accessible overflow trigger, optional loading state, section grouping, disabled actions, and neutral MUI semantic colors. UseactionColorSxfor an explicit host-theme override. The host owns the action list, callbacks, authorization, navigation, and request lifecycle.StickyActionFooterprovides semantic, locally sticky footer chrome for the final actions of a long form. It stays at the bottom of the host's scroll container while the form scrolls. The host supplies its actions, decides whether they are full width, and usessxfor any edge-to-edge bleed inside a padded surface.
Dialog layout, action placement, confirmation policy, and close behavior stay in host applications until multiple consumers share the same product contract. Likewise, user avatars and account menus stay downstream: hosts own identity loading, image fallbacks, authentication, sign-out effects, navigation, copy, menu contents, and touch/layout policy.
Feedback
Import StatusNotice from @bayonai/ui/feedback. It renders neutral
information, success, warning, and error presentation for strings or rich React
content. Consumers may supply host-owned action content, root sx, a
dataComponent marker, and an explicit role such as alert. Error notices
retain role="status" by default for compatibility. Stable
data-status-notice-part markers identify the icon, message, and optional
action so branded adapters do not depend on generated Material UI classes.
The package only renders action content. It deliberately does not invoke, disable, retry, sanitize, translate, log, or infer the meaning of messages or actions; host adapters retain request state, accessibility urgency, and user-facing error policy.
Uploads
Import FileUpload, FileRow, FileThumbnail, FileViewer, UploadProgressRing, and useUploadLock from @bayonai/ui/uploads.
UploadFilesupplies neutral file identity, name, MIME type, byte size and source URL. Generic components preserve extra host fields without depending on them.FileUploadowns picker interactions, a synchronous upload lock, busy/error UI, selection and separators. Supply controlledattachmentsandonUploadAttachment. The host updates the list when persistence completes.renderFileandrenderViewercompose the other components.RowShell,FieldIcon, andcolorslet the host preserve its detail layout without app imports inside the package.FileRowmakes thumbnail/name/size one preview target and composesSwipeActionRow.thumbnailandsizeLabelare slots; delete and preview callbacks receive the original file.FileThumbnailreceives a stableloadFile(file, signal)callback. It manages private raster thumbnails, loading/error fallback and owned object URL cleanup. Host-owned local blob URLs are not revoked.FileViewerreceivesloadFileandonDownload. File previews use raster/media decoders, sandboxed browser PDF support or escaped text; other formats offer download. PDF rendering depends on the browser/WebView. Loading, media-decoding and download failures are logged and displayed. File-specific state resets internally when the selected file changes.useUploadLockprevents repeated upload events before a React rerender. Existing non-task uploaders also reuse this hook through the host adapter.UploadProgressRingwraps a compact upload target with an accessible, determinate circular progress indicator. Supply the host-owned target aschildren, aprogresspercentage (ornullwhile idle), and a specificprogressLabel. It clamps the visual value to 0–100 and disables pointer events on the ring, so the host controls picker interaction and its disabled state. Use it when progress should stay attached to the object being changed, such as an avatar; keep error and recovery controls in the host near the target.
The host retains Firebase authentication, access checks, MIME/size validation, task mutations, offline queue ownership, filename-preserving downloads and user-facing error mapping. accept is a picker hint, not validation. UploadProgressRing deliberately does not start uploads, infer an error state, replace its target, or manage retries; that lifecycle belongs to the host.
No tooltips are added. Field rows retain their full-width separators; the populated uploader/file-list join retains the requested 80% divider.
Swipe
Import SwipeActionRow from @bayonai/ui/swipe.
Wrap row content and supply onDelete and canDelete. A left pull past 72px invokes the callback; the host decides whether this opens confirmation, starts undo, or deletes immediately. Keep an accessible button for keyboard/mouse deletion. The row does not own persistence or confirmation.
Vertical scrolling, right pulls, short gestures and cancellation do not invoke deletion. Permission is checked again on release. The immediate trailing click is consumed so a swipe cannot also open the preview. Horizontal movement uses a 12px direction threshold and a 104px maximum visual offset. Motion settles respect reduced-motion preferences.
This first extraction migrates attachment swipes. Task/subtask rows also have right-pull copy and drag ownership; migrate them after extending the shared action contract and preserving those behaviors.
Toasts
Import Toast, ActionToast, and useUpwardToastDismiss from @bayonai/ui/toasts. Toast supplies a generic themed surface, placement, live-region role and optional upward gesture dismissal. ActionToast adds message, actionLabel, actionAriaLabel, onAction, and optional actionDisabled; it can use a host-styled ToastComponent.
There are no automatic timers or transaction rules in the package. The host controls lifetime, undo eligibility, operation identity, and what dismissal means. Action button taps are excluded from drag initiation. A 64px upward pull dismisses; short pulls return to their origin. No action tooltips are added. Mobile top/bottom offsets are configurable; Thunderlist's AppToast adapter keeps its navigation offsets and branded surface.
Task deletion and move notifications now use ActionToast, with their original copy, undo callbacks and expiry timers retained in the app. Other AppToast consumers use the extracted shell via the existing adapter.
Drawers
Import MobileBottomDrawer, MobileBottomDrawerHeader, BottomDrawer, ResponsiveDrawer, useVisualViewportBottomInset, and getVisualViewportBottomInset from @bayonai/ui/drawers.
These components are now consumed by Thunderlist's existing drawer adapters. appearance supplies background, chrome, handle color, shadow and safe-area padding with neutral MUI defaults. Close tooltips are optional; accessible close labels remain present.
MobileBottomDrawer mounts open; a user close or dismiss signal animates before calling onClose (220ms, immediate callback with reduced motion). The host unmounts after that callback. Timers are cancelled on unmount; withdrawing a pending external dismiss cancels it. MUI continues to own focus restoration, nested-modal focus and scroll-aware touch dismissal. contentScrollMode selects container or child scroll ownership. Header actions and handle placement preserve the existing layouts.
BottomDrawer is a controlled bottom-anchored drawer, also used for desktop presentation in Thunderlist. Portal ownership is configurable and defaults to MUI's portal behavior; Thunderlist's adapters disable the portal for their route slots. ResponsiveDrawer chooses mobile below 900px and desktop otherwise; it forwards separate callbacks and a presentation argument to its content. It does not navigate or broadcast exclusive-modal events. Route/back-button behavior stays with the host.
The viewport hook reports the area obscured below the visual viewport and cleans up resize/scroll subscriptions. Host screens choose where to apply it; extraction does not automatically reposition every drawer for the keyboard.
Behavior acceptance checklist
Drawer extraction must include behavior, not just appearance:
- Bottom pull-to-dismiss with existing timing, velocity behavior and reduced motion.
- Scroll ownership: container versus child, nested content, vertical scroll at the top boundary.
- Horizontal action gestures must not trigger vertical sheet dismissal.
- Header/handle placement, safe areas, visual viewport and on-screen keyboard.
- Focus entry/restoration, Escape/backdrop dismissal and nested dialogs that preserve the parent sheet.
- Desktop bottom drawer versus mobile sheet; route/back-button and portal policy remain in host adapters.
The existing Thunderlist lifecycle, markup, responsive and viewport tests now exercise these package components through their adapters. Package tests also cover cancelled external dismissal and unmount during a user-close animation. Real native touch verification is still a separate release check. The legacy data-drawer-gesture-exclude marker alone is not proof of arbitration; the current sheet uses MUI's gesture implementation.
September 9 browser smoke check: a disposable harness using the actual source with React/MUI verified 1440px desktop and 390×844 mobile presentation, open/close, focus return to the opener, nested Dialog closure preserving its parent drawer, and constrained container scrolling without console errors. Child mode's hidden outer overflow was checked, but a scrolling child was not exercised. Physical touch gestures and native keyboard behavior remain release checks.
Validation
Run corepack pnpm --filter @bayonai/ui run release:check from the workspace
root before every release. It type-checks and tests the source, builds ESM,
CommonJS, and declaration artifacts, creates the npm tarball, installs that
tarball into an isolated consumer, and verifies each export with ESM, CommonJS,
and TypeScript resolution. CI runs the same gate.
After that command is green, follow the package release runbook at
docs/UI_PACKAGE_RELEASE.md.
Host attachment tests continue to exercise the package via thin adapters.
Browser/emulator rendering and gesture verification remains a release check for
the actual application targets; record that evidence in the release pull
request before publishing a supported public release.
Use a consumer application to exercise uploads, file previews, swipe deletion,
responsive drawers, toasts, error/disabled states, and themes. Thunderlist's
development-only /ui-playground route remains a consumer integration harness;
it is not part of this package or Bounded's product surface.
