najm-kit 2.10.0 → 2.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,82 @@
1
- # Changelog
1
+ # Changelog
2
+
3
+ ## 2.11.1 - 2026-08-11
4
+
5
+ - Fixed `NajmThemeProvider` precedence so an explicit runtime `mode` overrides
6
+ a preset stored in the design config. An explicit `preset` prop still wins,
7
+ allowing deliberately fixed-preset subtrees without producing mixed light
8
+ and dark application surfaces.
9
+
10
+ ## 2.11.0 - 2026-08-10
11
+
12
+ - Gave every keyboard-focusable primitive a visible focus ring, from one shared
13
+ source: `focusRingClasses` and `focusRingWithinClasses`, exported from the
14
+ root barrel so consumers stop hand-rolling app-level focus CSS. `TabsContent`
15
+ is keyboard-focusable (Radix sets `tabIndex={0}`) and previously showed no
16
+ indicator at all; `CollapsibleTrigger` now carries its own ring rather than
17
+ relying on whatever it wraps. `BaseInput` paints the ring on the wrapper via
18
+ `has-[:focus-visible]:`, which is what finally gives `NumberInput` and the
19
+ multi-select trigger an indicator — both suppress the inner control's ring,
20
+ so focusing the child element used to change only the border colour.
21
+ - Fixed `Dialog` focus restoration. The opener is captured in
22
+ `onOpenAutoFocus` and refocused on close when focus would otherwise land on
23
+ `<body>`, so dismissing a dialog with Escape returns the keyboard to the
24
+ control that opened it (WCAG 2.4.3) instead of stranding it at the top of
25
+ the document. A caller that calls `preventDefault()` on the close event still
26
+ owns focus entirely.
27
+ - Added `NCredentialsCard`, a one-time handover surface for freshly generated
28
+ secrets. Renders a description list of fields with monospaced, break-all
29
+ values, an optional header icon that defaults to a check mark, an optional
30
+ consumer `actions` slot, and a built-in Copy button. The Copy action resolves
31
+ text through `copyText` when supplied or joins `${label}: ${value}` with
32
+ `\n` in field order otherwise. The button is disabled while a clipboard
33
+ write is pending; on success it swaps to a check icon and `copiedLabel`,
34
+ on failure to a warning icon and `copyErrorLabel`, and either state reverts
35
+ to idle after roughly two seconds. Header and field icons are marked
36
+ decorative, status is announced through a polite `aria-live` region, and
37
+ the Copy button renders before consumer actions so a Done-style dismiss
38
+ stays the last tab stop. The component never rethrows: a missing
39
+ `navigator.clipboard`, a rejected or synchronously thrown `writeText`, and
40
+ a synchronously thrown `copyText` all land in the error state with
41
+ `onCopyError` invoked. State updates and revert timers are guarded so
42
+ unmounting during a pending copy, or starting a second copy while the
43
+ first success state is still showing, never fire stale setters. Logical
44
+ spacing properties keep the layout correct under `dir="rtl"` without a
45
+ consumer override, and each value carries `dir="auto"` so it resolves its own
46
+ text direction: credentials are weak-directionality strings, and inheriting an
47
+ ambient `rtl` paints `+1 555 0100` as `0100 555 1+` and `p@ssw0rd!` as
48
+ `!p@ssw0rd` while the DOM and the copied text stay correct. A genuinely Arabic
49
+ value still renders right-to-left. Packaged English fallbacks ship for the three action
50
+ labels only; every field label, title, and description is the consumer's
51
+ text. Exported as `NCredentialsCard`, `NCredentialField`,
52
+ `NCredentialsCardProps`, and `NCredentialsCardClassNames` from the root
53
+ barrel and from `packages/najm-kit/src/components/data-display`.
54
+ - Added a shared `surface` contract across `NLoadingState`, `NErrorState`, and
55
+ `NEmptyState`. `inline` (default) preserves existing behavior; `panel` adds a
56
+ centered minimum-height frame for table, card, dialog, and sheet bodies
57
+ without a page gutter or a landmark; `page` uses the configured page
58
+ spacing through `NPageLayout as="div"` so it never introduces a nested
59
+ `<main>`. `NLoadingState.fullScreen` keeps precedence over `surface`.
60
+ - Added `NForbiddenState` and `NNotFoundState` as first-class generic Kit
61
+ components. Both default to the page surface, ship token-backed default
62
+ icons (`ShieldOff`, `Compass`), and resolve title and description through
63
+ the new provider defaults. Neither knows the dashboard URL, renders a Next
64
+ `Link`, redirects, or writes route metadata.
65
+ - Added `feedbackDefaults` to `NajmUIProvider`. It is inherited by
66
+ `NajmNextUIProvider` and `NajmAppProvider` through TypeScript — no second
67
+ adapter prop or translation source. Labels resolve through the existing
68
+ structural `t` (most specific: explicit prop > literal default > translated
69
+ key > packaged English). Generic error body copy and empty description
70
+ deliberately have no packaged fallback so the no-provider render is
71
+ unchanged.
72
+ - Exported the new feedback components and props (`NLoadingStateProps`,
73
+ `NErrorStateProps`, `NEmptyStateProps`, `NForbiddenStateProps`,
74
+ `NNotFoundStateProps`, `NFeedbackSurface`, `NFeedbackDefaults`,
75
+ `NFeedbackLabels`, `NFeedbackLabelKeys`, `NFeedbackIconSize`) from the root
76
+ barrel and re-exported them directly from `najm-kit/app` so a Next Server
77
+ Component route can render them without authoring a local `"use client"`
78
+ wrapper. The root entry stays free of `"use client"` and continues to
79
+ share a single `NFeedbackDefaultsContext` across `index` and the adapters.
2
80
 
3
81
  ## 2.10.0 - 2026-08-10
4
82
 
package/README.md CHANGED
@@ -142,9 +142,9 @@ import { Form, FormInput, useNForm } from 'najm-kit';
142
142
  |----------|-----------|
143
143
  | Actions | NButton, IconButton, toggleVariants |
144
144
  | Forms | Input, Textarea, Label, Select, Checkbox, RadioGroup, Switch, DateInput, FileInput, ImageInput, AvatarInput |
145
- | Feedback | Alert, Badge, Progress, Spinner, Toast |
145
+ | Feedback | Alert, Badge, Progress, Spinner, Toast, NLoadingState, NErrorState, NEmptyState, NForbiddenState, NNotFoundState |
146
146
  | Layout | Card, Sheet, Dialog, Popover, DropdownMenu, Tabs |
147
- | Data | Table (NTable), StatCard, DetailList |
147
+ | Data | Table (NTable), StatCard, DetailList, CredentialsCard |
148
148
  | Overlays | Command palette, Tooltip, Toast |
149
149
 
150
150
  ## Images and avatars
@@ -275,6 +275,128 @@ Statuses are matched through one rule, exported as `normalizeStatusToken`, so
275
275
  key for colors, icons, and labels alike. Badge text is presentation: it renames
276
276
  nothing in the backend and validates no lifecycle transition.
277
277
 
278
+ ## Feedback states
279
+
280
+ Five public state components cover the reusable cases every application
281
+ otherwise repeats: `NLoadingState`, `NErrorState`, `NEmptyState`,
282
+ `NForbiddenState`, and `NNotFoundState`. They share one layout frame and one
283
+ provider-defaults channel, so an application configures its copy once and
284
+ every consumer below inherits it.
285
+
286
+ ### Surfaces
287
+
288
+ Three layouts, one prop. `surface` selects the frame:
289
+
290
+ | `surface` | Use it for | What it does |
291
+ | --- | --- | --- |
292
+ | `"inline"` (default) | A small slot inside an existing component | Legacy sizing, no landmark |
293
+ | `"panel"` | A table body, card body, dialog, or sheet | Centered with a minimum height, no page gutter, no landmark |
294
+ | `"page"` | A real route-level state | Uses page spacing from the design config; renders through a non-`<main>` root |
295
+
296
+ `NLoadingState.fullScreen` keeps its fixed viewport overlay regardless of
297
+ surface — it always wins.
298
+
299
+ ```tsx
300
+ import { NLoadingState, NErrorState, NEmptyState } from 'najm-kit';
301
+
302
+ // Inline (default): drop into a card or section.
303
+ <NLoadingState label="Loading orders..." />
304
+
305
+ // Panel: table body or dialog content.
306
+ <NEmptyState surface="panel" title="No orders yet" icon={Inbox} />
307
+
308
+ // Page: route-level empty state. Never introduces a second <main>.
309
+ <NErrorState
310
+ surface="page"
311
+ title="Dashboard unavailable"
312
+ message="We are working on it."
313
+ onRetry={() => refetch()}
314
+ />
315
+ ```
316
+
317
+ ### Provider defaults
318
+
319
+ Pass one `feedbackDefaults` map to `NajmUIProvider` (or to `NajmAppProvider`
320
+ through it) and every feedback state beneath uses it. There is one place for
321
+ loading, empty, error, retry, forbidden, and not-found labels, and a single
322
+ language change recomputes them all without remounting the tree.
323
+
324
+ ```tsx
325
+ import { NajmAppProvider } from 'najm-kit/app';
326
+
327
+ <NajmAppProvider
328
+ feedbackDefaults={{
329
+ labels: {
330
+ loadingLabel: 'Chargement…',
331
+ emptyTitle: 'Aucune donnée',
332
+ errorTitle: 'Une erreur est survenue',
333
+ retryLabel: 'Réessayer',
334
+ forbiddenTitle: 'Accès refusé',
335
+ forbiddenDescription: 'Vous n\'avez pas la permission.',
336
+ notFoundTitle: 'Page introuvable',
337
+ notFoundDescription: 'La page demandée n\'existe pas.',
338
+ },
339
+ labelKeys: {
340
+ emptyTitle: 'common.empty',
341
+ errorTitle: 'common.error',
342
+ },
343
+ }}
344
+ >
345
+ <App />
346
+ </NajmAppProvider>
347
+ ```
348
+
349
+ Resolution order, most specific first:
350
+
351
+ 1. An explicit component prop.
352
+ 2. A literal in `feedbackDefaults.labels`.
353
+ 3. A translated `feedbackDefaults.labelKeys` value resolved through the
354
+ provider's existing structural `t` function.
355
+ 4. The current packaged English fallback, when that field has one.
356
+
357
+ Generic `NErrorState.message` and `NEmptyState.description` deliberately have
358
+ no packaged fallback — the no-provider render must look the same as it did
359
+ before this contract shipped. A configured `errorMessage` opts the generic
360
+ error state into rendering a body; absent that opt-in, the existing
361
+ no-body render is preserved.
362
+
363
+ ### Forbidden and not-found
364
+
365
+ Two first-class preset states for the routes every application grows:
366
+
367
+ ```tsx
368
+ import { NForbiddenState, NNotFoundState } from 'najm-kit';
369
+
370
+ // Forbidden: provider copy + ShieldOff icon + page surface by default.
371
+ <NForbiddenState
372
+ action={<Link href="/dashboard">Back to dashboard</Link>}
373
+ />
374
+
375
+ // Not found: provider copy + Compass icon + page surface by default.
376
+ <NNotFoundState
377
+ action={<Link href="/dashboard">Back to dashboard</Link>}
378
+ />
379
+ ```
380
+
381
+ Both are presentation only. They do not know the dashboard URL, render a
382
+ Next `Link`, redirect, or write route metadata — those belong to the
383
+ application's `not-found.tsx` / `forbidden/page.tsx` files.
384
+
385
+ ### Root and `najm-kit/app` imports
386
+
387
+ Both entries export the same five state components. Pick the one that matches
388
+ your boundary:
389
+
390
+ ```tsx
391
+ // Client feature code: import from the root barrel.
392
+ import { NEmptyState } from 'najm-kit';
393
+
394
+ // Next Server Component route: import from najm-kit/app, which is the
395
+ // Client Component boundary. A route file can render a state component
396
+ // without authoring a local "use client" wrapper.
397
+ import { NNotFoundState } from 'najm-kit/app';
398
+ ```
399
+
278
400
  ## Global form development tools
279
401
 
280
402
  Enable schema-driven test values once on the full application provider. Every
@@ -378,6 +500,70 @@ Key behaviors:
378
500
  `AvatarInput` forwards every preview and accessibility prop unchanged while
379
501
  preserving its circular, size, fill, and camera-icon defaults.
380
502
 
503
+ ## Credentials handover
504
+
505
+ `NCredentialsCard` renders the recurring "show a freshly generated secret
506
+ once, let the operator hand it over, never show it again" surface. It owns
507
+ the frame, the description-list semantics, the copy flow, the failure
508
+ handling, and the accessible feedback. Every domain label — title,
509
+ description, field labels, action labels, and any translated toast — stays
510
+ with the application.
511
+
512
+ ```tsx
513
+ import { NCredentialsCard, NButton } from "najm-kit";
514
+ import { KeyRound, Phone } from "lucide-react";
515
+
516
+ <NCredentialsCard
517
+ title={t("staff.access.created")}
518
+ description={t("staff.access.oneTimeHint")}
519
+ fields={[
520
+ { label: t("common.phone"), value: credentials.phone, icon: Phone },
521
+ { label: t("staff.access.initialPassword"), value: credentials.password, icon: KeyRound },
522
+ ]}
523
+ copyLabel={t("common.copyDetails")}
524
+ copiedLabel={t("common.copied")}
525
+ copyErrorLabel={t("common.copyError")}
526
+ actions={<NButton onClick={() => pop()}>{t("common.done")}</NButton>}
527
+ />
528
+ ```
529
+
530
+ Behaviour worth knowing:
531
+
532
+ - `fields` renders as a `<dl>` of `<dt>`/`<dd>` pairs. Values default to
533
+ monospaced and mid-string wrapping so secrets stay readable on every
534
+ width.
535
+ - The header icon defaults to a check mark and is always rendered when a
536
+ header is shown. Pass `icon={SomeLucideIcon}` to replace it; pass any
537
+ supported `NIconSource` to swap in a logo, image, or remote URL.
538
+ - The Copy button resolves text through `copyText` when supplied, otherwise
539
+ joins `${label}: ${value}` with `\n` in field order. The button is
540
+ disabled while the clipboard write is pending. Success swaps the label to
541
+ `copiedLabel` and a check icon; failure swaps to `copyErrorLabel` and a
542
+ warning icon. Either state reverts to idle after roughly two seconds.
543
+ - The copy button renders before any consumer `actions` so a Done-style
544
+ dismiss stays the last tab stop and never gets pressed before the secret
545
+ is actually copied.
546
+ - Missing `navigator.clipboard`, rejected `writeText`, and synchronously
547
+ thrown `copyText` / `writeText` all land in the error state and call
548
+ `onCopyError` instead of rethrowing. State updates and revert timers are
549
+ guarded, so unmounting during a pending copy, or starting a second copy
550
+ while the first success state is still showing, never fire stale setters.
551
+ - Status is announced through a polite `aria-live` region. The visible swap
552
+ is the primary feedback — no toast is emitted by the component.
553
+ - Packaged English fallbacks exist for `copyLabel`, `copiedLabel`, and
554
+ `copyErrorLabel` only. Title, description, and every field label are the
555
+ application's text; a consumer that omits them gets no text, not English.
556
+ - Spacing uses logical properties only, so a `dir="rtl"` tree needs no
557
+ override. Each value also carries `dir="auto"`, isolating it from the
558
+ surrounding paragraph direction: a phone number or password inherited into an
559
+ RTL tree otherwise *paints* reordered (`+1 555 0100` as `0100 555 1+`) even
560
+ though the DOM and the copied text are correct. A value whose first strong
561
+ character is Arabic still renders right-to-left.
562
+
563
+ When you only want consumer buttons and no built-in copy, pass
564
+ `hideCopyAction`. Pass `copyText` to format the copied text differently
565
+ (one CSV line per field, a JSON blob, a single concatenated value, …).
566
+
381
567
  ## Formatting
382
568
 
383
569
  Pure formatters are available from the server-safe `najm-kit/format` entry.
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
2
+ import React__default, { ReactNode } from 'react';
3
3
  import { ZodTypeAny, TypeOf } from 'zod';
4
+ import { LucideIcon } from 'lucide-react';
4
5
 
5
6
  interface NBrandingValue {
6
7
  /** Used as the logo's `alt` when the logo does not set one. */
@@ -94,4 +95,85 @@ type FormDevTools<T extends ZodTypeAny = ZodTypeAny> = boolean | FormDevToolsCon
94
95
  /** Build form-shaped test values from a Zod object schema. */
95
96
  declare function buildFormFill<TSchema extends ZodTypeAny>(schema: TSchema, overrides?: FormFillOverrides): Partial<TypeOf<TSchema>>;
96
97
 
97
- export { type FormDevToolsOptions as F, type NBrandingInput as N, type FormDevTools as a, type FormDevToolsConfig as b, type FormFillOverride as c, type FormFillOverrides as d, type NBrandingEditorValue as e, type NBrandingPayload as f, NBrandingProvider as g, NBrandingStateProvider as h, type NBrandingStateProviderProps as i, type NBrandingValue as j, buildFormFill as k, useNBrandingEditor as l, normalizeBranding as n, useNBranding as u };
98
+ /**
99
+ * Shared surface a feedback state renders into.
100
+ *
101
+ * - `inline` (default) keeps each component's existing padding and sizing. No
102
+ * landmark, no page gutter.
103
+ * - `panel` is for table bodies, card bodies, dialogs, sheets, and nested
104
+ * surfaces. Centered with a minimum height, no landmark, no page gutter.
105
+ * - `page` is for actual route-level states. Uses page spacing from the design
106
+ * config, but renders through `NPageLayout as="div"` so it never introduces
107
+ * another `<main>` inside an already-paged shell.
108
+ */
109
+ type NFeedbackSurface = "inline" | "panel" | "page";
110
+
111
+ interface NLoadingStateProps {
112
+ label?: string;
113
+ className?: string;
114
+ fullScreen?: boolean;
115
+ spinnerVariant?: "default" | "circle" | "pinwheel" | "circle-filled" | "ellipsis" | "ring" | "bars";
116
+ spinnerSize?: number;
117
+ /**
118
+ * Layout surface. `inline` is the default and preserves the legacy look;
119
+ * `panel` is for table/dialog/sheet bodies; `page` uses page spacing
120
+ * without introducing a `<main>`. `fullScreen` takes precedence and renders
121
+ * the fixed viewport overlay regardless of surface.
122
+ */
123
+ surface?: NFeedbackSurface;
124
+ }
125
+ declare function NLoadingState({ label, className, fullScreen, spinnerVariant, spinnerSize, surface, }: NLoadingStateProps): react_jsx_runtime.JSX.Element;
126
+
127
+ interface NErrorStateProps {
128
+ title?: string;
129
+ message?: string;
130
+ onRetry?: () => void;
131
+ retryLabel?: string;
132
+ className?: string;
133
+ icon?: React__default.ReactNode;
134
+ /** Layout surface. See `NLoadingStateProps.surface`. */
135
+ surface?: NFeedbackSurface;
136
+ }
137
+ declare function NErrorState({ title, message, onRetry, retryLabel, className, icon, surface, }: NErrorStateProps): react_jsx_runtime.JSX.Element;
138
+
139
+ interface NEmptyStateProps {
140
+ title?: string;
141
+ description?: string;
142
+ icon?: React__default.ReactNode | LucideIcon;
143
+ action?: React__default.ReactNode;
144
+ className?: string;
145
+ /** Layout surface. See `NLoadingStateProps.surface`. */
146
+ surface?: NFeedbackSurface;
147
+ }
148
+ declare function NEmptyState({ title, description, icon, action, className, surface, }: NEmptyStateProps): react_jsx_runtime.JSX.Element;
149
+
150
+ /**
151
+ * First-class "access denied" state.
152
+ *
153
+ * Thin preset over `NEmptyState`. Defaults to the page surface and a token-
154
+ * backed `ShieldOff` icon, with title and description resolved from the
155
+ * provider's forbidden defaults. None of the routing, authorization,
156
+ * metadata, or framework-link behavior lives here — that is the
157
+ * application's responsibility.
158
+ */
159
+ interface NForbiddenStateProps extends NEmptyStateProps {
160
+ title?: string;
161
+ description?: string;
162
+ }
163
+ declare function NForbiddenState(props: NForbiddenStateProps): react_jsx_runtime.JSX.Element;
164
+
165
+ /**
166
+ * First-class "page not found" state.
167
+ *
168
+ * Thin preset over `NEmptyState`. Defaults to the page surface and a `Compass`
169
+ * icon, with title and description resolved from the provider's not-found
170
+ * defaults. None of the route-target, metadata, redirect, or framework-link
171
+ * behavior lives here.
172
+ */
173
+ interface NNotFoundStateProps extends NEmptyStateProps {
174
+ title?: string;
175
+ description?: string;
176
+ }
177
+ declare function NNotFoundState(props: NNotFoundStateProps): react_jsx_runtime.JSX.Element;
178
+
179
+ export { type FormDevToolsOptions as F, type NBrandingInput as N, NEmptyState as a, type NEmptyStateProps as b, NErrorState as c, type NErrorStateProps as d, type NFeedbackSurface as e, NForbiddenState as f, type NForbiddenStateProps as g, NLoadingState as h, type NLoadingStateProps as i, NNotFoundState as j, type NNotFoundStateProps as k, type FormDevTools as l, type FormDevToolsConfig as m, type FormFillOverride as n, type FormFillOverrides as o, type NBrandingEditorValue as p, type NBrandingPayload as q, NBrandingProvider as r, NBrandingStateProvider as s, type NBrandingStateProviderProps as t, type NBrandingValue as u, buildFormFill as v, normalizeBranding as w, useNBranding as x, useNBrandingEditor as y };
@@ -113,6 +113,69 @@ interface NBadgeDefaults {
113
113
  statusLabelKeys?: Record<string, string>;
114
114
  }
115
115
 
116
+ /**
117
+ * Optional literal labels, supplied as `feedbackDefaults.labels`. Each field
118
+ * is independent: an application can localize one feedback label without
119
+ * touching the others.
120
+ *
121
+ * `errorMessage` is the only key without a packaged fallback. A consumer that
122
+ * supplies one opts a generic error state into rendering a body; absent that
123
+ * opt-in, the no-provider behavior is unchanged.
124
+ */
125
+ interface NFeedbackLabels {
126
+ loadingLabel?: string;
127
+ emptyTitle?: string;
128
+ errorTitle?: string;
129
+ errorMessage?: string;
130
+ retryLabel?: string;
131
+ forbiddenTitle?: string;
132
+ forbiddenDescription?: string;
133
+ notFoundTitle?: string;
134
+ notFoundDescription?: string;
135
+ }
136
+ /**
137
+ * Map from a feedback field to a translation catalog key. Resolved through the
138
+ * provider's structural `t`. Optional per-field; a missing key falls through
139
+ * to the literal default, then to packaged English.
140
+ */
141
+ interface NFeedbackLabelKeys {
142
+ loadingLabel?: string;
143
+ emptyTitle?: string;
144
+ errorTitle?: string;
145
+ errorMessage?: string;
146
+ retryLabel?: string;
147
+ forbiddenTitle?: string;
148
+ forbiddenDescription?: string;
149
+ notFoundTitle?: string;
150
+ notFoundDescription?: string;
151
+ }
152
+ /**
153
+ * Provider-level defaults for the shared feedback components.
154
+ *
155
+ * Threaded through `NajmUIProvider.feedbackDefaults` and inherited by the
156
+ * `next` and `app` adapters. No second adapter prop or translation source:
157
+ * the same structural `t` every table and badge reads is what these labels
158
+ * route through.
159
+ */
160
+ interface NFeedbackDefaults {
161
+ /**
162
+ * Literal overrides. A field that is `undefined` here still resolves through
163
+ * `labelKeys` and the packaged English fallback.
164
+ */
165
+ labels?: NFeedbackLabels;
166
+ /**
167
+ * Catalog keys resolved through the provider's `t`. Memoize the object: a
168
+ * fresh identity on every render rebuilds the label bundle and re-renders
169
+ * every feedback state beneath the provider.
170
+ */
171
+ labelKeys?: NFeedbackLabelKeys;
172
+ /**
173
+ * Catalog prefix for translated keys. Keys are read as `<prefix>.<field>`.
174
+ * Defaults to `"common.feedback"`.
175
+ */
176
+ prefix?: string;
177
+ }
178
+
116
179
  /**
117
180
  * Theme and time zone, the two preferences that outlive a render.
118
181
  *
@@ -229,6 +292,16 @@ interface NajmUIProviderProps extends Omit<NajmPreferencesProviderProps, "childr
229
292
  * for the same reason as `t`.
230
293
  */
231
294
  badgeDefaults?: NBadgeDefaults;
295
+ /**
296
+ * Defaults for the shared feedback state components — loading, empty, error,
297
+ * forbidden, and not-found. Each label resolves through the provider's `t`
298
+ * the same way pagination and badge labels do, so the same translator
299
+ * reaches every state without a second bridge.
300
+ *
301
+ * Memoize the object: a fresh identity rebuilds the resolved bundle and
302
+ * re-renders every feedback state beneath.
303
+ */
304
+ feedbackDefaults?: NFeedbackDefaults;
232
305
  }
233
306
  /**
234
307
  * The one provider a Najm application mounts for UI concerns.
@@ -249,6 +322,6 @@ interface NajmUIProviderProps extends Omit<NajmPreferencesProviderProps, "childr
249
322
  * an application with a runtime theme editor hoist preferences above its
250
323
  * design context without forking this component.
251
324
  */
252
- declare function NajmUIProvider({ children, design, initialDesign, className, t, paginationKeyPrefix, tableDefaults, badgeDefaults, initialTheme, initialTimeZone, onThemeChange, onTimeZoneChange, normalizeTimeZone, }: NajmUIProviderProps): react_jsx_runtime.JSX.Element;
325
+ declare function NajmUIProvider({ children, design, initialDesign, className, t, paginationKeyPrefix, tableDefaults, badgeDefaults, feedbackDefaults, initialTheme, initialTimeZone, onThemeChange, onTimeZoneChange, normalizeTimeZone, }: NajmUIProviderProps): react_jsx_runtime.JSX.Element;
253
326
 
254
- export { type BadgeColor as B, DEFAULT_TIME_ZONE as D, type NajmUIProviderProps as N, type NIconSource as a, type BadgeShape as b, Badge as c, type BadgeIcon as d, type BadgeLook as e, type BadgeProps as f, type BadgeSize as g, type BadgeVariant as h, NBadge as i, type NBadgeDefaults as j, type NBadgeLook as k, type NBadgeProps as l, NIcon as m, type NIconProps as n, type NTableDefaults as o, NTableDefaultsProvider as p, type NajmPreferencesContextValue as q, NajmPreferencesProvider as r, type NajmPreferencesProviderProps as s, NajmUIProvider as t, badgeColorVariants as u, badgeVariants as v, useNTableDefaults as w, useNajmPreferencesContext as x, useNajmTheme as y, useNajmTimeZone as z };
327
+ export { useNajmPreferencesContext as A, type BadgeColor as B, useNajmTheme as C, DEFAULT_TIME_ZONE as D, useNajmTimeZone as E, type NFeedbackDefaults as N, type NFeedbackLabelKeys as a, type NFeedbackLabels as b, type NajmUIProviderProps as c, type NIconSource as d, type BadgeShape as e, Badge as f, type BadgeIcon as g, type BadgeLook as h, type BadgeProps as i, type BadgeSize as j, type BadgeVariant as k, NBadge as l, type NBadgeDefaults as m, type NBadgeLook as n, type NBadgeProps as o, NIcon as p, type NIconProps as q, type NTableDefaults as r, NTableDefaultsProvider as s, type NajmPreferencesContextValue as t, NajmPreferencesProvider as u, type NajmPreferencesProviderProps as v, NajmUIProvider as w, badgeColorVariants as x, badgeVariants as y, useNTableDefaults as z };
@@ -1,11 +1,13 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { Translations } from 'najm-i18n';
3
- import { F as FormDevToolsOptions, N as NBrandingInput } from '../formFill-BcH-m9Kf.js';
3
+ import { F as FormDevToolsOptions, N as NBrandingInput } from '../NNotFoundState-D1CLLLxc.js';
4
+ export { a as NEmptyState, b as NEmptyStateProps, c as NErrorState, d as NErrorStateProps, e as NFeedbackSurface, f as NForbiddenState, g as NForbiddenStateProps, h as NLoadingState, i as NLoadingStateProps, j as NNotFoundState, k as NNotFoundStateProps } from '../NNotFoundState-D1CLLLxc.js';
4
5
  import { NajmNextUIProviderProps } from './next.js';
6
+ export { N as NFeedbackDefaults, a as NFeedbackLabelKeys, b as NFeedbackLabels } from '../NajmUIProvider-cNCb8Tpn.js';
5
7
  import 'react';
6
8
  import 'zod';
9
+ import 'lucide-react';
7
10
  import 'next/image';
8
- import '../NajmUIProvider-BnReyojl.js';
9
11
  import '../design-types-Rkpt8Pg1.js';
10
12
  import '../paginationLabels-dZLSNxfo.js';
11
13
  import 'class-variance-authority/types';
@@ -1,9 +1,12 @@
1
1
  'use client';
2
- import { FormDevToolsProvider, NBrandingStateProvider, NajmFormatProvider } from '../chunk-HML2JZXT.mjs';
3
- import { NajmNextUIProvider } from '../chunk-EUQOTPLV.mjs';
4
- import '../chunk-FDONJASN.mjs';
5
- import '../chunk-KVZACF4G.mjs';
2
+ import { FormDevToolsProvider, NBrandingStateProvider, NajmFormatProvider } from '../chunk-ZXIVSNXV.mjs';
3
+ export { NEmptyState, NErrorState, NForbiddenState, NLoadingState, NNotFoundState } from '../chunk-ZXIVSNXV.mjs';
4
+ import { NajmNextUIProvider } from '../chunk-T6RL7TSQ.mjs';
5
+ import '../chunk-6UDJNTJ6.mjs';
6
+ import '../chunk-ONE7GLIH.mjs';
7
+ import '../chunk-LK3SMYUP.mjs';
6
8
  import '../chunk-JABLSOQN.mjs';
9
+ import '../chunk-TFHWLE7N.mjs';
7
10
  import * as React from 'react';
8
11
  import { I18nProvider, useTranslation } from 'najm-i18n/react';
9
12
  import { jsx } from 'react/jsx-runtime';
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React__default from 'react';
3
3
  import { ImageProps } from 'next/image';
4
- import { N as NajmUIProviderProps } from '../NajmUIProvider-BnReyojl.js';
4
+ import { c as NajmUIProviderProps } from '../NajmUIProvider-cNCb8Tpn.js';
5
5
  import '../design-types-Rkpt8Pg1.js';
6
6
  import '../paginationLabels-dZLSNxfo.js';
7
7
  import 'class-variance-authority/types';
@@ -1,3 +1,3 @@
1
- export { NNextImage, NajmNextUIProvider, NextLinkAdapter, useNextNavigationAdapter } from '../chunk-EUQOTPLV.mjs';
2
- import '../chunk-FDONJASN.mjs';
3
- import '../chunk-KVZACF4G.mjs';
1
+ export { NNextImage, NajmNextUIProvider, NextLinkAdapter, useNextNavigationAdapter } from '../chunk-T6RL7TSQ.mjs';
2
+ import '../chunk-6UDJNTJ6.mjs';
3
+ import '../chunk-LK3SMYUP.mjs';
@@ -0,0 +1,117 @@
1
+ import { cn } from './chunk-LK3SMYUP.mjs';
2
+ import * as React from 'react';
3
+ import { createContext, useContext } from 'react';
4
+ import { OverlayScrollbars } from 'overlayscrollbars';
5
+ import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
6
+ import { jsx } from 'react/jsx-runtime';
7
+
8
+ function assignRef(ref, node) {
9
+ if (!ref) return;
10
+ if (typeof ref === "function") ref(node);
11
+ else ref.current = node;
12
+ }
13
+ function applyViewportLayout(node, axis) {
14
+ node.style.width = "100%";
15
+ node.style.height = "100%";
16
+ node.style.minWidth = "0";
17
+ node.style.minHeight = "0";
18
+ node.style.overflowX = axis === "x" || axis === "both" ? "auto" : "hidden";
19
+ node.style.overflowY = axis === "y" || axis === "both" ? "auto" : "hidden";
20
+ }
21
+ function najmScrollOptions(axis, autoHide, options) {
22
+ return {
23
+ scrollbars: { theme: "os-theme-najm", autoHide, autoHideDelay: 500, clickScroll: true },
24
+ overflow: {
25
+ x: axis === "x" || axis === "both" ? "scroll" : "hidden",
26
+ y: axis === "y" || axis === "both" ? "scroll" : "hidden"
27
+ },
28
+ ...options
29
+ };
30
+ }
31
+ function useNajmScrollViewport({
32
+ axis = "y",
33
+ autoHide = "never",
34
+ options
35
+ } = {}) {
36
+ const hostRef = React.useRef(null);
37
+ const viewportRef = React.useRef(null);
38
+ React.useEffect(() => {
39
+ const target = hostRef.current;
40
+ const viewport = viewportRef.current;
41
+ if (!target || !viewport) return;
42
+ const instance = OverlayScrollbars(
43
+ { target, elements: { viewport } },
44
+ najmScrollOptions(axis, autoHide, options)
45
+ );
46
+ const containWheel = (event) => event.stopPropagation();
47
+ viewport.addEventListener("wheel", containWheel, { passive: true });
48
+ return () => {
49
+ viewport.removeEventListener("wheel", containWheel);
50
+ instance.destroy();
51
+ };
52
+ }, [axis, autoHide, options]);
53
+ return { hostRef, viewportRef };
54
+ }
55
+ function NajmScroll({ className, axis = "y", autoHide = "never", viewportRef, events, options, element, children, style, ...props }) {
56
+ return /* @__PURE__ */ jsx(
57
+ OverlayScrollbarsComponent,
58
+ {
59
+ className: cn(className),
60
+ style: {
61
+ ...style,
62
+ overflow: "hidden",
63
+ minHeight: 0,
64
+ minWidth: 0
65
+ },
66
+ element,
67
+ defer: true,
68
+ options: najmScrollOptions(axis, autoHide, options || void 0),
69
+ events: {
70
+ ...events,
71
+ initialized: (instance, ...rest) => {
72
+ const viewport = instance.elements().viewport;
73
+ applyViewportLayout(viewport, axis);
74
+ assignRef(viewportRef, viewport);
75
+ events?.initialized?.(instance, ...rest);
76
+ },
77
+ updated: (instance, ...rest) => {
78
+ applyViewportLayout(instance.elements().viewport, axis);
79
+ events?.updated?.(instance, ...rest);
80
+ },
81
+ destroyed: (instance, ...rest) => {
82
+ assignRef(viewportRef, null);
83
+ events?.destroyed?.(instance, ...rest);
84
+ }
85
+ },
86
+ ...props,
87
+ children
88
+ }
89
+ );
90
+ }
91
+ var TableStoreContext = createContext(null);
92
+ var useTableStore = { use: {} };
93
+ var handler = {
94
+ get: (_, prop) => () => {
95
+ const store = useContext(TableStoreContext);
96
+ if (!store) throw new Error("useTableStore must be used within NTable");
97
+ return store.use[prop]();
98
+ }
99
+ };
100
+ useTableStore.use = new Proxy({}, handler);
101
+ function formatJsonValue(value) {
102
+ if (typeof value === "string") return value;
103
+ try {
104
+ return JSON.stringify(value ?? null, null, 2) ?? String(value);
105
+ } catch {
106
+ return String(value);
107
+ }
108
+ }
109
+ function NTableJson() {
110
+ const viewMode = useTableStore.use.viewMode();
111
+ const renderJson = useTableStore.use.renderJson();
112
+ const jsonValue = useTableStore.use.jsonValue();
113
+ if (viewMode !== "json") return null;
114
+ return /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col min-h-0 overflow-hidden", children: renderJson?.() ?? /* @__PURE__ */ jsx(NajmScroll, { axis: "both", className: "h-full min-h-0 rounded-md border border-border bg-muted/40", children: /* @__PURE__ */ jsx("pre", { className: "p-4 font-mono text-xs leading-relaxed text-foreground", children: formatJsonValue(jsonValue) }) }) });
115
+ }
116
+
117
+ export { NTableJson, NajmScroll, TableStoreContext, useNajmScrollViewport, useTableStore };