@dloizides/ui-feedback

中文文档
1.22.0发布于 2026/11/10MITTypeScriptESM + CJS近 30 天下载 3373

Themable, brand-agnostic React Native (RN-web) feedback components — ErrorState, EmptyListState, LoadingFallback, PageSkeleton, ConfirmDialog — wired through a small injectable theme/translate provider. Part of the dloizides.com shared UI kit.

原始使用文档
文档目录10 节
  1. Components
  2. Helpers
  3. Install
  4. Setup
  5. Usage
  6. Toasts — createToastBus()
  7. Error boundary — <AppErrorBoundary>
  8. i18n keys
  9. testIDs
  10. License

@dloizides/ui-feedback

Themable, brand-agnostic React Native (RN-web) feedback components for the dloizides.com portfolio. The components read theme colours and translated copy from a small injectable provider, so each app supplies its own theme + i18n without the package depending on any app's internals.

Components

ComponentPurpose
ErrorStateGeneric error message with an optional retry button.
EmptyListStateEmpty-collection message with an optional navigating call-to-action.
LoadingFallbackLightweight spinner for React.lazy / Suspense boundaries.
PageSkeletonShimmer skeleton for full-page loads (list or card variant).
ConfirmDialogModal confirm/cancel dialog with destructive + loading states.
ToastHostTransient-toast overlay; pair it with createToastBus().
WarmingOverlayBranded cold-start "warming up…" overlay.
AppErrorBoundaryTop-level error boundary with a themed, announced fallback.

Helpers

HelperPurpose
createToastBus()Framework-agnostic toast emit bus — the ToastHost subscribe port.

Install

npm install @dloizides/ui-feedback

Peer dependencies: react >= 18, react-native >= 0.74 (use react-native-web on web).

Setup

Mount FeedbackUiProvider once, high in your tree, below your app's own theme provider. Adapt your local useTheme() + translate helper to the provider:

import { FeedbackUiProvider } from '@dloizides/ui-feedback';
import { useRouter } from 'expo-router';
import { FM } from './localization/helpers';
import { useTheme } from './theme/hooks/useTheme';

const FeedbackUiAdapter = ({ children }: { children: React.ReactNode }) => {
  const { theme } = useTheme();
  const router = useRouter();
  return (
    <FeedbackUiProvider theme={theme} t={FM} navigate={(route) => router.push(route)}>
      {children}
    </FeedbackUiProvider>
  );
};

The injected theme only needs colors.{background,surfaceElevated,text,textSecondary,border}, palette.primary['500'] and semantic.error['500'] — any resolved theme with those fields is structurally compatible.

Usage

import { ErrorState, ConfirmDialog, LoadingFallback } from '@dloizides/ui-feedback';

<ErrorState message="Could not load data" onRetry={refetch} />
<LoadingFallback fullScreen />
<ConfirmDialog
  visible={open}
  title="Delete item?"
  message="This cannot be undone."
  destructive
  onConfirm={onDelete}
  onCancel={() => setOpen(false)}
/>

Toasts — createToastBus()

The toast pipeline is two halves: createToastBus() emits, <ToastHost> renders. A bus's subscribe is the host's subscribe port.

The bus is framework-agnostic on purpose — no React import, no hook, no provider — so a React Query mutation callback can raise a toast with no component context in scope. It carries no styling (ToastHost themes every toast from the provider), and the text you pass is already localized by you; the bus never sees a translation key.

// src/lib/toastBus.ts — one module-level instance per app
import { createToastBus } from '@dloizides/ui-feedback';
export const toastBus = createToastBus();
// once, in the shell:
<ToastHost subscribe={toastBus.subscribe} />

// anywhere — no provider needed:
useMutation({ onSuccess: () => toastBus.success(FM('menu.saved')) });
toastBus.error(FM('menu.saveFailed'));
toastBus.warning(FM('menu.needsReview'));
toastBus.emit(FM('menu.queued'), 'info');   // explicit type; defaults to 'info'

Emitting with no host mounted is a silent no-op. Every subscriber receives every emit, and subscribe returns an unsubscribe.

Error boundary — <AppErrorBoundary>

Wrap your app once. The fallback renders through ErrorState, so it themes from the provider, and it announces itself (role="alert" + accessibilityLiveRegion="assertive").

<AppErrorBoundary
  labels={{ title: FM('errorBoundary.title'), message: FM('errorBoundary.message') }}
  showDetails={__DEV__}
  onError={(error, info) => captureException(error, { extra: { componentStack: info.componentStack } })}
  recover={(error) => isChunkLoadError(error) && attemptChunkRecovery()}
  onReload={reloadPage}
>
  <App />
</AppErrorBoundary>
PropPurpose
onError(error, info)Reporting sink (Sentry / logger). Called for every caught error, before recover.
recover(error)Return true if handled (e.g. a guarded reload is under way) → shows the updating screen instead of the error screen.
showDetailsRender the error message in the fallback (dev-only). Default false.
labelsPartial, pre-localized ErrorBoundaryLabels; anything omitted uses the English defaults in DEFAULT_ERROR_BOUNDARY_LABELS.
testIDPrefixPrefix for every testID. Default error-boundary.
fallback(state)Escape hatch — replaces the built-in fallback entirely. Receives { error, recovering, retry }.
onReloadOptional secondary Reload action. The kit never touches window, so you inject the reload.

The package imports no i18n runtime — localize at the call site and pass strings.

i18n keys

The components call the injected t(key) with these keys (provide them in each app's locale files): common.retry, common.retryHint, common.confirm, common.cancel, common.confirmHint, common.cancelHint, loadingFallback.label, loadingFallback.hint, pageSkeleton.loadingLabel, pageSkeleton.loadingHint. EmptyListState takes its keys as props.

testIDs

Default testIDs (FEEDBACK_TEST_IDS) match the strings the consuming apps already used, so existing Playwright selectors keep working: error-state, error-state-retry, empty-list-cta, loading-fallback, page-skeleton, confirm-dialog, confirm-button, cancel-confirm-button, notification-toast, warming-overlay, error-boundary.

AppErrorBoundary derives its ids from testIDPrefix (default error-boundary), preserving the selectors the app-local copies used: error-boundary, error-boundary-message, error-boundary-details, error-boundary-retry-button, error-boundary-reload-button, error-boundary-updating.

License

MIT

版本与发布记录

@dloizides/ui-feedback 共发布 28 个版本,首次发布于 2026/06/16;过去 12 个月发布了 28 个版本。

版本发布日期距今
1.22.0latest2026/11/102 天前
1.21.02026/11/102 天前
1.20.02026/11/048 天前
1.19.22026/10/2815 天前
1.19.12026/10/2815 天前
1.19.02026/10/2815 天前

关于 @dloizides/ui-feedback 的常见问题

@dloizides/ui-feedback 是什么?

Themable, brand-agnostic React Native (RN-web) feedback components — ErrorState, EmptyListState, LoadingFallback, PageSkeleton, ConfirmDialog — wired through a small injectable theme/translate provider. Part of the dloizides.com shared UI kit.

如何安装 @dloizides/ui-feedback?

在项目目录运行 npm install @dloizides/ui-feedback,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18.0.0)、react-native(>=0.74.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@dloizides/ui-feedback 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@dloizides/ui-feedback 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@dloizides/ui-feedback 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@dloizides/ui-feedback 可以商用吗?

@dloizides/ui-feedback 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dloizides/ui-feedback 的下载量有多少?

根据 npm 官方下载统计,@dloizides/ui-feedback 在 2026/10/13 – 2026/11/11 这 30 天内被下载 3,373 次,日均约 112 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dloizides/ui-feedback 还在维护吗?

最新版本 1.22.0 发布于 2026/11/10(2 天前);至今共发布 28 个版本,其中过去 12 个月发布了 28 个;首次发布于 2026/06/16。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。