najm-kit 2.7.3 → 2.8.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 +29 -3
- package/README.md +124 -6
- package/dist/{NajmUIProvider-IFU3dFkn.d.ts → NajmUIProvider-BSbXaqak.d.ts} +1 -1
- package/dist/adapters/app.d.ts +10 -4
- package/dist/adapters/app.mjs +8 -5
- package/dist/adapters/next.d.ts +2 -2
- package/dist/{cardPagination-A6h8vXuk.d.ts → cardPagination-Bwf3tEeN.d.ts} +16 -4
- package/dist/chunk-GHMR45H3.mjs +333 -0
- package/dist/{chunk-GPHWBOSP.mjs → chunk-JABLSOQN.mjs} +11 -1
- package/dist/{chunk-XGDMPI5U.mjs → chunk-OUS7OYVA.mjs} +11 -9
- package/dist/{NBrandingContext-dkADu8JS.d.ts → formFill-BcH-m9Kf.d.ts} +16 -1
- package/dist/format.d.ts +34 -1
- package/dist/format.mjs +1 -1
- package/dist/index.d.ts +74 -13
- package/dist/index.mjs +138 -29
- package/dist/{paginationLabels-DgHutNWz.d.ts → paginationLabels-dZLSNxfo.d.ts} +8 -1
- package/dist/query.d.ts +3 -3
- package/dist/query.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-VKQIRB7F.mjs +0 -116
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 2.
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.8.1 - 2026-08-08
|
|
4
|
+
|
|
5
|
+
- Added schema-driven form development tools to `NajmAppProvider`. Passing
|
|
6
|
+
`formDevTools` enables F8 filling for every `NForm` and `WizardForm` without
|
|
7
|
+
an application helper or additional provider.
|
|
8
|
+
- Added built-in Zod 4 form-value generation with per-form overrides for
|
|
9
|
+
relation fields and other application-owned values. Existing explicit
|
|
10
|
+
`devTools.enabled` and `devTools.fill` usage remains supported.
|
|
11
|
+
|
|
12
|
+
## 2.8.0 - 2026-08-08
|
|
13
|
+
|
|
14
|
+
- Added server-safe `najm-kit/format` helpers for currency minor units,
|
|
15
|
+
numbers, percentages, dates, times, relative time, tokens, local date inputs,
|
|
16
|
+
and slugs. Client applications can use the same contract reactively through
|
|
17
|
+
`NajmFormatProvider` and `useNajmFormat`.
|
|
18
|
+
- Added the server-safe `najm-kit/pagination` offset protocol, including bounded
|
|
19
|
+
page creation, total-aware continuation, probe-row continuation for APIs
|
|
20
|
+
without totals, and query cleanup that preserves meaningful `false` and `0`.
|
|
21
|
+
- Added the optional-peer `najm-kit/query` entry with offset infinite-query and
|
|
22
|
+
responsive paged/card-list hooks, plus shared card-pagination adapters and
|
|
23
|
+
localized continuation labels.
|
|
24
|
+
- Extended `NajmAppProvider` with formatting locale, currency, and placeholder
|
|
25
|
+
configuration so applications can bind language, time zone, and formatting
|
|
26
|
+
without another host bridge provider.
|
|
27
|
+
- Added shared media-query/card-viewport helpers and avatar-source utilities.
|
|
28
|
+
|
|
29
|
+
## 2.6.2
|
|
4
30
|
|
|
5
31
|
- Added `NSidebarProvider` and `useNSidebar`, so sidebar state can be read from a distance. `NSidebar` renders beside the page content rather than around it, which left applications hand-rolling a context to hand `setMobileOpen` down to a page header — a wrapper component plus an aliased import at every call site. Wrap the shell in `NSidebarProvider` and `NPageHeader` now resolves both `onSidebarOpen` and `mobileBreakpoint` from it, so a header nested anywhere below renders a working mobile trigger with no props threaded to it. Also exports the `NSidebarContextValue` type.
|
|
6
32
|
- `NSidebar` resolves its open and collapsed state as explicit prop → surrounding provider → internal state. Passing `collapsed`, `mobileOpen`, `onCollapsedChange`, or `onMobileOpenChange` keeps behaving exactly as before, and a sidebar with no provider around it still owns its own state, so this is additive for every existing consumer.
|
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ Changing the state updates the complete theme immediately. Use
|
|
|
123
123
|
`stringifyNajmThemeConfig(theme)` when persisting it, and parse settings loaded
|
|
124
124
|
from an API or local storage with `parseNajmThemeConfig` before applying them.
|
|
125
125
|
|
|
126
|
-
## Components
|
|
126
|
+
## Components
|
|
127
127
|
|
|
128
128
|
Import from `najm-kit`:
|
|
129
129
|
|
|
@@ -145,7 +145,42 @@ import { Form, FormInput, useNForm } from 'najm-kit';
|
|
|
145
145
|
| Feedback | Alert, Badge, Progress, Spinner, Toast |
|
|
146
146
|
| Layout | Card, Sheet, Dialog, Popover, DropdownMenu, Tabs |
|
|
147
147
|
| Data | Table (NTable), StatCard, DetailList |
|
|
148
|
-
| Overlays | Command palette, Tooltip, Toast |
|
|
148
|
+
| Overlays | Command palette, Tooltip, Toast |
|
|
149
|
+
|
|
150
|
+
## Global form development tools
|
|
151
|
+
|
|
152
|
+
Enable schema-driven test values once on the full application provider. Every
|
|
153
|
+
`NForm` and `WizardForm` below it then fills from its Zod schema when F8 is
|
|
154
|
+
pressed; applications do not need a second provider or a form-fill helper.
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
import { NajmAppProvider } from "najm-kit/app";
|
|
158
|
+
|
|
159
|
+
<NajmAppProvider formDevTools>
|
|
160
|
+
<App />
|
|
161
|
+
</NajmAppProvider>;
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Pass a boolean to control it from application settings:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
<NajmAppProvider formDevTools={formFillEnabled}>
|
|
168
|
+
<App />
|
|
169
|
+
</NajmAppProvider>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Forms with live relation options can override only those fields. The provider
|
|
173
|
+
still owns enablement and Najm Kit still owns schema traversal and generation.
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<NForm
|
|
177
|
+
schema={orderSchema}
|
|
178
|
+
devTools={{ overrides: { customerId: customerOptions } }}
|
|
179
|
+
onSubmit={saveOrder}
|
|
180
|
+
>
|
|
181
|
+
{/* fields */}
|
|
182
|
+
</NForm>
|
|
183
|
+
```
|
|
149
184
|
|
|
150
185
|
## ImageInput and AvatarInput
|
|
151
186
|
|
|
@@ -212,10 +247,93 @@ Key behaviors:
|
|
|
212
247
|
a newer value, and object URLs created by the component are tracked so
|
|
213
248
|
consumer-owned blob URLs are never revoked.
|
|
214
249
|
|
|
215
|
-
`AvatarInput` forwards every preview and accessibility prop unchanged while
|
|
216
|
-
preserving its circular, size, fill, and camera-icon defaults.
|
|
217
|
-
|
|
218
|
-
##
|
|
250
|
+
`AvatarInput` forwards every preview and accessibility prop unchanged while
|
|
251
|
+
preserving its circular, size, fill, and camera-icon defaults.
|
|
252
|
+
|
|
253
|
+
## Formatting
|
|
254
|
+
|
|
255
|
+
Pure formatters are available from the server-safe `najm-kit/format` entry.
|
|
256
|
+
Money values are integer minor units and use the currency's own exponent (for
|
|
257
|
+
example MAD has two decimals, JPY zero, and KWD three).
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import { formatCurrency, formatDate, slugify } from 'najm-kit/format';
|
|
261
|
+
|
|
262
|
+
formatCurrency(12_500, { locale: 'fr-MA', currency: 'MAD' });
|
|
263
|
+
formatDate('2026-08-08T20:00:00Z', {
|
|
264
|
+
locale: 'fr-MA',
|
|
265
|
+
timeZone: 'Africa/Casablanca',
|
|
266
|
+
});
|
|
267
|
+
slugify('Najm Format & Pagination');
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Client code can use the active locale, time zone, currency, and placeholder
|
|
271
|
+
through `useNajmFormat`. `NajmAppProvider` mounts the format provider for you:
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
import { NajmAppProvider } from 'najm-kit/app';
|
|
275
|
+
import { useNajmFormat } from 'najm-kit';
|
|
276
|
+
|
|
277
|
+
<NajmAppProvider
|
|
278
|
+
translations={translations}
|
|
279
|
+
currency="MAD"
|
|
280
|
+
locales={{ en: 'en-MA', fr: 'fr-MA' }}
|
|
281
|
+
>
|
|
282
|
+
<App />
|
|
283
|
+
</NajmAppProvider>
|
|
284
|
+
|
|
285
|
+
function Total({ value }: { value: number }) {
|
|
286
|
+
return <span>{useNajmFormat().money(value)}</span>;
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Offset pagination and queries
|
|
291
|
+
|
|
292
|
+
`najm-kit/pagination` is server-safe and framework-independent. It accepts
|
|
293
|
+
endpoints that return either `{ rows, total }` or a bare row array. When no
|
|
294
|
+
total exists it probes for one extra row; when a total exists continuation is
|
|
295
|
+
calculated without another request.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import {
|
|
299
|
+
createOffsetPagination,
|
|
300
|
+
fetchOffsetPage,
|
|
301
|
+
} from 'najm-kit/pagination';
|
|
302
|
+
|
|
303
|
+
const pagination = createOffsetPagination(pageIndex, pageSize);
|
|
304
|
+
const page = await fetchOffsetPage(
|
|
305
|
+
({ limit, offset }) => api.orders.list({ limit, offset }),
|
|
306
|
+
pagination,
|
|
307
|
+
);
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
React Query consumers install the optional `@tanstack/react-query` peer and use
|
|
311
|
+
the isolated `najm-kit/query` entry. `useResponsiveOffsetList` resolves numbered
|
|
312
|
+
desktop paging versus card continuation and exposes props that plug directly
|
|
313
|
+
into `NTable` and `createCardPagination`.
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
import { NTable, createCardPagination } from 'najm-kit';
|
|
317
|
+
import { useResponsiveOffsetList } from 'najm-kit/query';
|
|
318
|
+
|
|
319
|
+
const list = useResponsiveOffsetList({
|
|
320
|
+
queryKey: ['orders'],
|
|
321
|
+
fetchPage: ({ limit, offset }) => api.orders.list({ limit, offset }),
|
|
322
|
+
strategy: 'paged',
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
<NTable
|
|
326
|
+
data={list.data}
|
|
327
|
+
columns={columns}
|
|
328
|
+
manualPagination
|
|
329
|
+
pageCount={list.pageCount}
|
|
330
|
+
pagination={list.pagination}
|
|
331
|
+
onPaginationChange={list.onPaginationChange}
|
|
332
|
+
cardPagination={createCardPagination(list, labels)}
|
|
333
|
+
/>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Hooks
|
|
219
337
|
|
|
220
338
|
```tsx
|
|
221
339
|
import { useKeyboard } from 'najm-kit';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React$1 from 'react';
|
|
3
3
|
import React__default from 'react';
|
|
4
|
-
import { b as NTablePaginationLabels, N as NajmTranslate } from './paginationLabels-
|
|
4
|
+
import { b as NTablePaginationLabels, N as NajmTranslate } from './paginationLabels-dZLSNxfo.js';
|
|
5
5
|
|
|
6
6
|
type NajmMode = 'light' | 'dark';
|
|
7
7
|
type NajmAccent = 'neutral' | 'emerald' | 'green' | 'slate' | 'blue' | 'violet';
|
package/dist/adapters/app.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { Translations } from 'najm-i18n';
|
|
3
|
-
import { N as NBrandingInput } from '../
|
|
3
|
+
import { F as FormDevToolsOptions, N as NBrandingInput } from '../formFill-BcH-m9Kf.js';
|
|
4
4
|
import { NajmNextUIProviderProps } from './next.js';
|
|
5
5
|
import 'react';
|
|
6
|
-
import '
|
|
7
|
-
import '../
|
|
6
|
+
import 'zod';
|
|
7
|
+
import '../NajmUIProvider-BSbXaqak.js';
|
|
8
|
+
import '../paginationLabels-dZLSNxfo.js';
|
|
8
9
|
|
|
9
10
|
/** Branding shown by the kit's chrome. Purely presentational values. */
|
|
10
11
|
interface NajmAppBranding {
|
|
@@ -13,6 +14,11 @@ interface NajmAppBranding {
|
|
|
13
14
|
logoCollapsed?: string | null;
|
|
14
15
|
}
|
|
15
16
|
interface NajmAppProviderProps extends Omit<NajmNextUIProviderProps, 't'> {
|
|
17
|
+
/**
|
|
18
|
+
* Enables schema-driven form filling for every `NForm` and `WizardForm`.
|
|
19
|
+
* `true` uses F8; an options object can choose another shortcut.
|
|
20
|
+
*/
|
|
21
|
+
formDevTools?: boolean | FormDevToolsOptions;
|
|
16
22
|
/**
|
|
17
23
|
* Catalog for `najm-i18n`. Supplying it mounts an `I18nProvider` and derives
|
|
18
24
|
* the pagination labels from it, so `t` is not a prop here — the provider
|
|
@@ -99,6 +105,6 @@ interface NajmAppProviderProps extends Omit<NajmNextUIProviderProps, 't'> {
|
|
|
99
105
|
* state machine of its own above this provider. The controlled `design` and
|
|
100
106
|
* `branding` props still work for applications that already do.
|
|
101
107
|
*/
|
|
102
|
-
declare function NajmAppProvider({ translations, initialLanguage, defaultLanguage, languageEndpoint, appName, initialBranding, ...props }: NajmAppProviderProps): react_jsx_runtime.JSX.Element;
|
|
108
|
+
declare function NajmAppProvider({ translations, initialLanguage, defaultLanguage, languageEndpoint, appName, initialBranding, formDevTools, ...props }: NajmAppProviderProps): react_jsx_runtime.JSX.Element;
|
|
103
109
|
|
|
104
110
|
export { type NajmAppBranding, NajmAppProvider, type NajmAppProviderProps };
|
package/dist/adapters/app.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { NBrandingStateProvider, NajmFormatProvider } from '../chunk-
|
|
2
|
+
import { FormDevToolsProvider, NBrandingStateProvider, NajmFormatProvider } from '../chunk-GHMR45H3.mjs';
|
|
3
3
|
import { NajmNextUIProvider } from '../chunk-IRFFSAO2.mjs';
|
|
4
4
|
import '../chunk-USZUOJMK.mjs';
|
|
5
5
|
import '../chunk-KVZACF4G.mjs';
|
|
6
|
-
import '../chunk-
|
|
6
|
+
import '../chunk-JABLSOQN.mjs';
|
|
7
7
|
import * as React from 'react';
|
|
8
8
|
import { I18nProvider, useTranslation } from 'najm-i18n/react';
|
|
9
9
|
import { jsx } from 'react/jsx-runtime';
|
|
@@ -72,6 +72,7 @@ function NajmAppProvider({
|
|
|
72
72
|
languageEndpoint = DEFAULT_LANGUAGE_ENDPOINT,
|
|
73
73
|
appName,
|
|
74
74
|
initialBranding,
|
|
75
|
+
formDevTools,
|
|
75
76
|
...props
|
|
76
77
|
}) {
|
|
77
78
|
const persistLanguage = React.useCallback(
|
|
@@ -91,8 +92,10 @@ function NajmAppProvider({
|
|
|
91
92
|
[languageEndpoint]
|
|
92
93
|
);
|
|
93
94
|
const seeded = appName ? { appName, ...initialBranding } : initialBranding;
|
|
94
|
-
if (!translations)
|
|
95
|
-
|
|
95
|
+
if (!translations) {
|
|
96
|
+
return /* @__PURE__ */ jsx(FormDevToolsProvider, { value: formDevTools, children: /* @__PURE__ */ jsx(NajmAppNoI18n, { ...props, initialBranding: seeded }) });
|
|
97
|
+
}
|
|
98
|
+
return /* @__PURE__ */ jsx(FormDevToolsProvider, { value: formDevTools, children: /* @__PURE__ */ jsx(
|
|
96
99
|
I18nProvider,
|
|
97
100
|
{
|
|
98
101
|
translations,
|
|
@@ -101,7 +104,7 @@ function NajmAppProvider({
|
|
|
101
104
|
onLanguageChange: persistLanguage,
|
|
102
105
|
children: /* @__PURE__ */ jsx(NajmAppUI, { ...props, initialBranding: seeded })
|
|
103
106
|
}
|
|
104
|
-
);
|
|
107
|
+
) });
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
export { NajmAppProvider };
|
package/dist/adapters/next.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
|
-
import { N as NajmUIProviderProps } from '../NajmUIProvider-
|
|
4
|
-
import '../paginationLabels-
|
|
3
|
+
import { N as NajmUIProviderProps } from '../NajmUIProvider-BSbXaqak.js';
|
|
4
|
+
import '../paginationLabels-dZLSNxfo.js';
|
|
5
5
|
|
|
6
6
|
interface NextLinkAdapterProps extends Record<string, any> {
|
|
7
7
|
href: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { N as NajmTranslate, a as NTableCardPagination } from './paginationLabels-
|
|
1
|
+
import { N as NajmTranslate, a as NTableCardPagination } from './paginationLabels-dZLSNxfo.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* How a list continues.
|
|
@@ -34,11 +34,18 @@ interface CardPaginationLabels {
|
|
|
34
34
|
itemsLoaded?: (count: number) => string;
|
|
35
35
|
}
|
|
36
36
|
declare const DEFAULT_CARD_PAGINATION_KEY_PREFIX = "common.pagination";
|
|
37
|
+
type DefaultCardPaginationPrefix = typeof DEFAULT_CARD_PAGINATION_KEY_PREFIX;
|
|
38
|
+
/** The three catalog keys `buildCardPaginationLabels` reads under `Prefix`. */
|
|
39
|
+
type CardPaginationKey<Prefix extends string = DefaultCardPaginationPrefix> = `${Prefix}.itemsLoaded` | `${Prefix}.loadMoreError` | `${Prefix}.retryLoadMore`;
|
|
37
40
|
/**
|
|
38
41
|
* Projects a translator onto the three card-continuation labels, matching
|
|
39
42
|
* `buildPaginationLabels` — same prefix convention, same key-per-field naming.
|
|
43
|
+
*
|
|
44
|
+
* The keys are named in the type, not just built at runtime, so an application
|
|
45
|
+
* whose `t` is typed to a generated union of its catalog can pass it directly
|
|
46
|
+
* and have the three keys verified against that union.
|
|
40
47
|
*/
|
|
41
|
-
declare function buildCardPaginationLabels(t: NajmTranslate
|
|
48
|
+
declare function buildCardPaginationLabels<Prefix extends string = DefaultCardPaginationPrefix>(t: NajmTranslate<CardPaginationKey<Prefix>>, prefix?: Prefix): CardPaginationLabels;
|
|
42
49
|
/**
|
|
43
50
|
* Builds the `cardPagination` prop from the state a paged list already holds.
|
|
44
51
|
*
|
|
@@ -47,7 +54,12 @@ declare function buildCardPaginationLabels(t: NajmTranslate, prefix?: string): C
|
|
|
47
54
|
* reports the size it wants through `onPaginationChange`; `all` renders exactly
|
|
48
55
|
* what it is given and shows no controls; `infinite` is the only one that needs
|
|
49
56
|
* continuation wiring, which is why the other two ignore the labels entirely.
|
|
57
|
+
*
|
|
58
|
+
* The second argument takes a translator as well as a label bundle. That is the
|
|
59
|
+
* common case — a list page has `t` in hand and nothing else to say about these
|
|
60
|
+
* three strings — and passing it here rather than pre-building labels also means
|
|
61
|
+
* the lookups only happen in `infinite` mode, where they are rendered.
|
|
50
62
|
*/
|
|
51
|
-
declare function createCardPagination(state: CardPaginationState, labels?: CardPaginationLabels): NTableCardPagination;
|
|
63
|
+
declare function createCardPagination<Prefix extends string = DefaultCardPaginationPrefix>(state: CardPaginationState, labels?: CardPaginationLabels | NajmTranslate<CardPaginationKey<Prefix>>, prefix?: Prefix): NTableCardPagination;
|
|
52
64
|
|
|
53
|
-
export { type
|
|
65
|
+
export { type CardPaginationKey as C, DEFAULT_CARD_PAGINATION_KEY_PREFIX as D, type ListStrategy as L, type ResolvedListMode as R, type CardPaginationLabels as a, type CardPaginationState as b, buildCardPaginationLabels as c, createCardPagination as d };
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { useNajmPreferencesContext } from './chunk-USZUOJMK.mjs';
|
|
2
|
+
import { humanizeToken, DEFAULT_PLACEHOLDER, formatRelativeTime, formatTime, formatDateTime, formatDate, formatPercent, formatNumber, formatCurrency } from './chunk-JABLSOQN.mjs';
|
|
3
|
+
import * as React2 from 'react';
|
|
4
|
+
import { createContext, useContext, useMemo, useState, useCallback } from 'react';
|
|
5
|
+
import { jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function normalizeBranding(input) {
|
|
8
|
+
if (!input) return {};
|
|
9
|
+
const value = {};
|
|
10
|
+
const expanded = input.logoExpanded ?? input.sidebarLogoExpandedPath;
|
|
11
|
+
const collapsed = input.logoCollapsed ?? input.sidebarLogoCollapsedPath;
|
|
12
|
+
if (input.appName !== void 0) value.appName = input.appName;
|
|
13
|
+
if (input.logoFallback !== void 0) value.logoFallback = input.logoFallback;
|
|
14
|
+
if (input.logoHref !== void 0) value.logoHref = input.logoHref;
|
|
15
|
+
if (expanded !== void 0) value.logoExpanded = expanded;
|
|
16
|
+
if (collapsed !== void 0) value.logoCollapsed = collapsed;
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
var NBrandingContext = createContext(null);
|
|
20
|
+
function useNBranding() {
|
|
21
|
+
return useContext(NBrandingContext);
|
|
22
|
+
}
|
|
23
|
+
function NBrandingProvider({
|
|
24
|
+
children,
|
|
25
|
+
appName,
|
|
26
|
+
logoExpanded,
|
|
27
|
+
logoCollapsed,
|
|
28
|
+
logoFallback,
|
|
29
|
+
logoHref
|
|
30
|
+
}) {
|
|
31
|
+
const value = useMemo(
|
|
32
|
+
() => ({ appName, logoExpanded, logoCollapsed, logoFallback, logoHref }),
|
|
33
|
+
[appName, logoExpanded, logoCollapsed, logoFallback, logoHref]
|
|
34
|
+
);
|
|
35
|
+
return /* @__PURE__ */ jsx(NBrandingContext.Provider, { value, children });
|
|
36
|
+
}
|
|
37
|
+
var NBrandingEditorContext = createContext(null);
|
|
38
|
+
function useNBrandingEditor() {
|
|
39
|
+
return useContext(NBrandingEditorContext);
|
|
40
|
+
}
|
|
41
|
+
function NBrandingStateProvider({
|
|
42
|
+
children,
|
|
43
|
+
branding,
|
|
44
|
+
initialBranding
|
|
45
|
+
}) {
|
|
46
|
+
const [state, setState] = useState(
|
|
47
|
+
() => normalizeBranding(initialBranding ?? branding)
|
|
48
|
+
);
|
|
49
|
+
const setBranding = useCallback((patch) => {
|
|
50
|
+
const marks = normalizeBranding(patch);
|
|
51
|
+
setState((current) => ({ ...current, ...marks }));
|
|
52
|
+
}, []);
|
|
53
|
+
const controlled = useMemo(
|
|
54
|
+
() => branding ? normalizeBranding(branding) : void 0,
|
|
55
|
+
[branding]
|
|
56
|
+
);
|
|
57
|
+
const resolved = controlled ?? state;
|
|
58
|
+
const editor = useMemo(
|
|
59
|
+
() => ({ branding: resolved, setBranding: branding ? noop : setBranding }),
|
|
60
|
+
[resolved, branding, setBranding]
|
|
61
|
+
);
|
|
62
|
+
return /* @__PURE__ */ jsx(NBrandingEditorContext.Provider, { value: editor, children: /* @__PURE__ */ jsx(NBrandingProvider, { ...resolved, children }) });
|
|
63
|
+
}
|
|
64
|
+
function noop() {
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/components/form/formFill.ts
|
|
68
|
+
var CITIES = ["Casablanca", "Rabat", "Marrakesh", "Tangier"];
|
|
69
|
+
var SCHOOL_LEVELS = ["Primary", "Middle school", "Secondary school"];
|
|
70
|
+
var CLOTHING_SIZES = ["6 years", "8 years", "10 years", "12 years"];
|
|
71
|
+
function pick(values) {
|
|
72
|
+
if (values.length === 0) return void 0;
|
|
73
|
+
return values[Math.floor(Math.random() * values.length)];
|
|
74
|
+
}
|
|
75
|
+
function randomDigits(length) {
|
|
76
|
+
return Array.from({ length }, () => Math.floor(Math.random() * 10)).join("");
|
|
77
|
+
}
|
|
78
|
+
function randomToken(length = 8) {
|
|
79
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
80
|
+
return Array.from(
|
|
81
|
+
{ length },
|
|
82
|
+
() => alphabet[Math.floor(Math.random() * alphabet.length)]
|
|
83
|
+
).join("");
|
|
84
|
+
}
|
|
85
|
+
function asZodLike(schema) {
|
|
86
|
+
return schema && typeof schema === "object" ? schema : void 0;
|
|
87
|
+
}
|
|
88
|
+
function definition(schema) {
|
|
89
|
+
const current = asZodLike(schema);
|
|
90
|
+
return current?._def ?? current?.def ?? {};
|
|
91
|
+
}
|
|
92
|
+
function schemaKind(schema) {
|
|
93
|
+
const currentDefinition = definition(schema);
|
|
94
|
+
const raw = currentDefinition.typeName ?? currentDefinition.type;
|
|
95
|
+
if (typeof raw !== "string") return "";
|
|
96
|
+
return raw.replace(/^Zod/, "").toLowerCase();
|
|
97
|
+
}
|
|
98
|
+
function unwrap(schema) {
|
|
99
|
+
const seen = /* @__PURE__ */ new Set();
|
|
100
|
+
let current = asZodLike(schema);
|
|
101
|
+
while (current && !seen.has(current)) {
|
|
102
|
+
seen.add(current);
|
|
103
|
+
const kind = schemaKind(current);
|
|
104
|
+
const currentDefinition = definition(current);
|
|
105
|
+
const inner = currentDefinition.innerType ?? currentDefinition.schema ?? (kind === "pipe" ? currentDefinition.out ?? currentDefinition.in : void 0);
|
|
106
|
+
if (!inner || typeof inner !== "object") break;
|
|
107
|
+
current = inner;
|
|
108
|
+
}
|
|
109
|
+
return current;
|
|
110
|
+
}
|
|
111
|
+
function objectShape(schema) {
|
|
112
|
+
const current = unwrap(schema);
|
|
113
|
+
if (schemaKind(current) !== "object") return null;
|
|
114
|
+
const shape = current?.shape ?? definition(current).shape;
|
|
115
|
+
return typeof shape === "function" ? shape() : shape ?? null;
|
|
116
|
+
}
|
|
117
|
+
function arrayElement(schema) {
|
|
118
|
+
const current = unwrap(schema);
|
|
119
|
+
if (schemaKind(current) !== "array") return null;
|
|
120
|
+
const currentDefinition = definition(current);
|
|
121
|
+
const element = current?.element ?? currentDefinition.element ?? currentDefinition.type;
|
|
122
|
+
return element && typeof element === "object" ? element : null;
|
|
123
|
+
}
|
|
124
|
+
function enumValues(schema) {
|
|
125
|
+
const current = unwrap(schema);
|
|
126
|
+
const currentDefinition = definition(current);
|
|
127
|
+
const kind = schemaKind(current);
|
|
128
|
+
if (kind === "literal") {
|
|
129
|
+
const values2 = currentDefinition.values;
|
|
130
|
+
if (Array.isArray(values2)) return values2;
|
|
131
|
+
return "value" in currentDefinition ? [currentDefinition.value] : [];
|
|
132
|
+
}
|
|
133
|
+
if (kind !== "enum" && kind !== "nativeenum") return [];
|
|
134
|
+
const entries = currentDefinition.entries;
|
|
135
|
+
if (entries && typeof entries === "object") return Object.values(entries);
|
|
136
|
+
const values = currentDefinition.values;
|
|
137
|
+
if (Array.isArray(values)) return values;
|
|
138
|
+
return Array.isArray(current?.options) ? [...current.options] : [];
|
|
139
|
+
}
|
|
140
|
+
function stringFormat(schema) {
|
|
141
|
+
const currentDefinition = definition(unwrap(schema));
|
|
142
|
+
if (typeof currentDefinition.format === "string") return currentDefinition.format;
|
|
143
|
+
const checks = Array.isArray(currentDefinition.checks) ? currentDefinition.checks : [];
|
|
144
|
+
for (const check of checks) {
|
|
145
|
+
if (!check || typeof check !== "object") continue;
|
|
146
|
+
const value = check;
|
|
147
|
+
const format = value.format ?? value.kind;
|
|
148
|
+
if (typeof format === "string") return format;
|
|
149
|
+
}
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
function resolveOverride(override, fieldName) {
|
|
153
|
+
if (typeof override === "function") return override(fieldName);
|
|
154
|
+
if (Array.isArray(override)) {
|
|
155
|
+
const value = pick(override);
|
|
156
|
+
if (value && typeof value === "object" && "value" in value) {
|
|
157
|
+
return value.value;
|
|
158
|
+
}
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
return override;
|
|
162
|
+
}
|
|
163
|
+
function localDateInput(date = /* @__PURE__ */ new Date()) {
|
|
164
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
165
|
+
}
|
|
166
|
+
function birthDate(child) {
|
|
167
|
+
const date = /* @__PURE__ */ new Date();
|
|
168
|
+
date.setFullYear(date.getFullYear() - (child ? 10 : 35));
|
|
169
|
+
return localDateInput(date);
|
|
170
|
+
}
|
|
171
|
+
function fieldValue(fieldName, schema, siblingFields) {
|
|
172
|
+
const key = fieldName.toLowerCase();
|
|
173
|
+
const current = unwrap(schema);
|
|
174
|
+
const kind = schemaKind(current);
|
|
175
|
+
const format = stringFormat(current);
|
|
176
|
+
const values = enumValues(current);
|
|
177
|
+
if (values.length) return pick(values);
|
|
178
|
+
if (kind === "boolean") return true;
|
|
179
|
+
if (kind === "number" || kind === "bigint") {
|
|
180
|
+
if (key.includes("sortorder")) return 1;
|
|
181
|
+
if (key.includes("quantity")) return 5;
|
|
182
|
+
return 10;
|
|
183
|
+
}
|
|
184
|
+
if (format === "uuid" || key.endsWith("uuid")) {
|
|
185
|
+
return "9cc2c93f-f545-4e07-9f77-f79f08a71dd5";
|
|
186
|
+
}
|
|
187
|
+
if (format === "email" || key.includes("email")) {
|
|
188
|
+
return `test-${randomToken()}@example.com`;
|
|
189
|
+
}
|
|
190
|
+
if (format === "url" || key === "image" || key.includes("imageurl") || key.endsWith("url")) {
|
|
191
|
+
return `https://picsum.photos/seed/${randomToken()}/800/600`;
|
|
192
|
+
}
|
|
193
|
+
if (key === "month") return `${localDateInput().slice(0, 7)}-01`;
|
|
194
|
+
if (key === "registrationdate") return localDateInput();
|
|
195
|
+
if (format === "date" || key.includes("dateofbirth")) {
|
|
196
|
+
const child = siblingFields.has("schoolLevel") || siblingFields.has("clothingSize");
|
|
197
|
+
return birthDate(child);
|
|
198
|
+
}
|
|
199
|
+
if (key.endsWith("id") || key.endsWith("by")) return "";
|
|
200
|
+
if (key === "name") {
|
|
201
|
+
if (siblingFields.has("sku")) return `Test product ${randomToken(4)}`;
|
|
202
|
+
if (siblingFields.has("slug")) return `Test category ${randomToken(4)}`;
|
|
203
|
+
return "Test User";
|
|
204
|
+
}
|
|
205
|
+
if (key.includes("legalname")) return "Test User";
|
|
206
|
+
if (key.includes("phone")) return `+2126${randomDigits(8)}`;
|
|
207
|
+
if (key.endsWith("cin")) return `AB${randomDigits(6)}`;
|
|
208
|
+
if (key.includes("address")) return `10 Test Street, ${pick(CITIES)}`;
|
|
209
|
+
if (key.includes("schoollevel")) return pick(SCHOOL_LEVELS);
|
|
210
|
+
if (key.includes("clothingsize")) return pick(CLOTHING_SIZES);
|
|
211
|
+
if (key.includes("shoesize")) return "36";
|
|
212
|
+
if (key.includes("relationship")) return "Legal guardian";
|
|
213
|
+
if (key.includes("activationtargetmad")) return "7500";
|
|
214
|
+
if (key === "slug") return `test-${randomToken(6)}`;
|
|
215
|
+
if (key === "sku") return `TEST-${randomToken(8).toUpperCase()}`;
|
|
216
|
+
if (key.includes("amountmad") || key.includes("pricemad") || key.includes("limitmad") || key.includes("targetmad")) {
|
|
217
|
+
return "100.00";
|
|
218
|
+
}
|
|
219
|
+
if (key.includes("reason")) return "Generated for form testing.";
|
|
220
|
+
if (key.includes("notes")) return "Generated form testing note.";
|
|
221
|
+
if (key.includes("description")) return "Generated description for form testing.";
|
|
222
|
+
if (key.includes("code")) return randomToken(8).toUpperCase();
|
|
223
|
+
return `Test ${fieldName}`;
|
|
224
|
+
}
|
|
225
|
+
function buildFormFill(schema, overrides = {}) {
|
|
226
|
+
const shape = objectShape(schema) ?? {};
|
|
227
|
+
const siblingFields = new Set(Object.keys(shape));
|
|
228
|
+
const output = {};
|
|
229
|
+
for (const [fieldName, fieldSchema] of Object.entries(shape)) {
|
|
230
|
+
if (Object.prototype.hasOwnProperty.call(overrides, fieldName)) {
|
|
231
|
+
output[fieldName] = resolveOverride(overrides[fieldName], fieldName);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const nestedShape = objectShape(fieldSchema);
|
|
235
|
+
if (nestedShape) {
|
|
236
|
+
output[fieldName] = buildFormFill(fieldSchema);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const element = arrayElement(fieldSchema);
|
|
240
|
+
if (element) {
|
|
241
|
+
output[fieldName] = objectShape(element) ? [buildFormFill(element)] : [fieldValue(fieldName, element, siblingFields)];
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
output[fieldName] = fieldValue(fieldName, fieldSchema, siblingFields);
|
|
245
|
+
}
|
|
246
|
+
return output;
|
|
247
|
+
}
|
|
248
|
+
var DEFAULT_FORM_DEV_TOOLS = {
|
|
249
|
+
enabled: false,
|
|
250
|
+
shortcut: "F8"
|
|
251
|
+
};
|
|
252
|
+
var FormDevToolsContext = createContext(
|
|
253
|
+
DEFAULT_FORM_DEV_TOOLS
|
|
254
|
+
);
|
|
255
|
+
function normalizeFormDevTools(value) {
|
|
256
|
+
if (typeof value === "boolean") {
|
|
257
|
+
return { ...DEFAULT_FORM_DEV_TOOLS, enabled: value };
|
|
258
|
+
}
|
|
259
|
+
if (!value) return DEFAULT_FORM_DEV_TOOLS;
|
|
260
|
+
return {
|
|
261
|
+
enabled: value.enabled ?? true,
|
|
262
|
+
shortcut: value.shortcut ?? DEFAULT_FORM_DEV_TOOLS.shortcut
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function FormDevToolsProvider({
|
|
266
|
+
children,
|
|
267
|
+
value
|
|
268
|
+
}) {
|
|
269
|
+
const resolved = useMemo(
|
|
270
|
+
() => normalizeFormDevTools(value),
|
|
271
|
+
[value]
|
|
272
|
+
);
|
|
273
|
+
return /* @__PURE__ */ jsx(FormDevToolsContext.Provider, { value: resolved, children });
|
|
274
|
+
}
|
|
275
|
+
function useResolvedFormDevTools(schema, local) {
|
|
276
|
+
const global = useContext(FormDevToolsContext);
|
|
277
|
+
const options = typeof local === "object" ? local : void 0;
|
|
278
|
+
const enabled = typeof local === "boolean" ? local : options?.enabled ?? global.enabled;
|
|
279
|
+
const shortcut = options?.shortcut ?? global.shortcut;
|
|
280
|
+
const fill = options?.fill ?? (schema ? () => buildFormFill(schema, options?.overrides) : void 0);
|
|
281
|
+
return { enabled, shortcut, fill };
|
|
282
|
+
}
|
|
283
|
+
var NajmFormatContext = React2.createContext(
|
|
284
|
+
null
|
|
285
|
+
);
|
|
286
|
+
function NajmFormatProvider({
|
|
287
|
+
children,
|
|
288
|
+
locale,
|
|
289
|
+
currency,
|
|
290
|
+
timeZone,
|
|
291
|
+
placeholder = DEFAULT_PLACEHOLDER
|
|
292
|
+
}) {
|
|
293
|
+
const preferences = useNajmPreferencesContext();
|
|
294
|
+
const resolvedTimeZone = timeZone ?? preferences?.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
295
|
+
const value = React2.useMemo(() => {
|
|
296
|
+
const config = {
|
|
297
|
+
locale,
|
|
298
|
+
timeZone: resolvedTimeZone,
|
|
299
|
+
currency,
|
|
300
|
+
placeholder
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
locale,
|
|
304
|
+
timeZone: resolvedTimeZone,
|
|
305
|
+
currency,
|
|
306
|
+
placeholder,
|
|
307
|
+
config,
|
|
308
|
+
money: (minorUnits) => formatCurrency(minorUnits, config),
|
|
309
|
+
number: (value_, options) => formatNumber(value_, config, options),
|
|
310
|
+
percent: (value_, digits) => formatPercent(value_, config, digits),
|
|
311
|
+
date: (value_, options) => formatDate(value_, config, options),
|
|
312
|
+
dateTime: (value_) => formatDateTime(value_, config),
|
|
313
|
+
time: (value_) => formatTime(value_, config),
|
|
314
|
+
relativeTime: (value_) => formatRelativeTime(value_, config),
|
|
315
|
+
humanize: humanizeToken
|
|
316
|
+
};
|
|
317
|
+
}, [locale, resolvedTimeZone, currency, placeholder]);
|
|
318
|
+
return /* @__PURE__ */ jsx(NajmFormatContext.Provider, { value, children });
|
|
319
|
+
}
|
|
320
|
+
function useNajmFormatContext() {
|
|
321
|
+
return React2.useContext(NajmFormatContext);
|
|
322
|
+
}
|
|
323
|
+
function useNajmFormat() {
|
|
324
|
+
const value = React2.useContext(NajmFormatContext);
|
|
325
|
+
if (!value) {
|
|
326
|
+
throw new Error(
|
|
327
|
+
"useNajmFormat must be rendered under a NajmFormatProvider or NajmAppProvider."
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
return value;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export { FormDevToolsProvider, NBrandingProvider, NBrandingStateProvider, NajmFormatProvider, buildFormFill, normalizeBranding, useNBranding, useNBrandingEditor, useNajmFormat, useNajmFormatContext, useResolvedFormDevTools };
|
|
@@ -100,5 +100,15 @@ function formatRelativeTime(value, { locale, placeholder = DEFAULT_PLACEHOLDER }
|
|
|
100
100
|
function humanizeToken(value) {
|
|
101
101
|
return value.trim().replace(/[_-]+/g, " ").replace(/\s+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
102
102
|
}
|
|
103
|
+
function localDateInput(date = /* @__PURE__ */ new Date()) {
|
|
104
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
105
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
106
|
+
return `${date.getFullYear()}-${month}-${day}`;
|
|
107
|
+
}
|
|
108
|
+
function slugify(value, { upperCase = false, maxLength = 160 } = {}) {
|
|
109
|
+
const normalized = value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength);
|
|
110
|
+
const slug = normalized || crypto.randomUUID().slice(0, 8);
|
|
111
|
+
return upperCase ? slug.toUpperCase() : slug;
|
|
112
|
+
}
|
|
103
113
|
|
|
104
|
-
export { DEFAULT_PLACEHOLDER, formatCurrency, formatDate, formatDateTime, formatNumber, formatPercent, formatRelativeTime, formatTime, humanizeToken };
|
|
114
|
+
export { DEFAULT_PLACEHOLDER, formatCurrency, formatDate, formatDateTime, formatNumber, formatPercent, formatRelativeTime, formatTime, humanizeToken, localDateInput, slugify };
|