@nxgt/material 1.0.4 → 1.0.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steve Tsala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -3,14 +3,47 @@
3
3
  React component library: primitives, form fields, layout shell, `DataTable`,
4
4
  drag-and-drop, i18n, and `Icon`. Tailwind CSS v4 + Base UI.
5
5
 
6
+ ## At a glance
7
+
8
+ - **148 catalogue entries in 12 categories** ([Components](#components)).
9
+ They cover primitives and overlays as well as data tables, filters, kanban,
10
+ calendars, charts, chat, rich-text and code editors, PDF and media players.
11
+ - **Form fields ship in pairs.** Each one has a standalone component
12
+ (`TextField`) and a React Hook Form wrapper (`TextFormField`). Both are in
13
+ the same barrel.
14
+ - **Helpers** ([Helpers](#helpers)): `cn`, `clientOnly`, date, number,
15
+ storage and URL utilities, a Zod error map, and Redux Toolkit slice
16
+ factories in `/lib`. Theme, i18n-aware formatting and option hooks are in
17
+ `/hooks`.
18
+ - **i18n** ([i18n](#i18n)): an `I18nProvider` with ICU messages and shipped
19
+ `en` / `fr` strings. Your app merges in its own resources.
20
+ - **SSR-safe barrels** ([SSR](#ssr)): no browser globals at import, and
21
+ browser-only widgets are wrapped in `clientOnly`.
22
+ - **Vue sister**: `@nxgt/material-vue` uses the same names. The catalogue's
23
+ last column gives the Vue export for each entry.
24
+
25
+ ## Contents
26
+
27
+ - [Install](#install)
28
+ - [Setup](#setup)
29
+ - [Subpaths](#subpaths)
30
+ - [Usage](#usage)
31
+ - [Components](#components)
32
+ - [Helpers](#helpers)
33
+ - [SSR](#ssr)
34
+ - [Traps](#traps)
35
+ - [Development](#development)
36
+ - [License](#license)
37
+
6
38
  ## Install
7
39
 
8
40
  ```bash
9
41
  bun add @nxgt/material
10
42
  ```
11
43
 
12
- Public on npmjs; no token. **Required peers** the app must resolve the same
13
- copy, or hooks throw and tokens come from two builds:
44
+ It is public on npmjs, so no token is needed. **Required peers**: the app must
45
+ resolve the same copy of each, otherwise hooks throw and tokens come from two
46
+ builds.
14
47
 
15
48
  ```bash
16
49
  bun add react react-dom react-hook-form react-redux @reduxjs/toolkit @tanstack/react-table react-use
@@ -19,10 +52,10 @@ bun add react react-dom react-hook-form react-redux @reduxjs/toolkit @tanstack/r
19
52
  | Peer | Used by |
20
53
  | --- | --- |
21
54
  | `react`, `react-dom` `^19` | everything |
22
- | `react-hook-form` | every `*FormField` |
23
- | `react-redux`, `@reduxjs/toolkit` | `DataTable`, filter, gallery, kanban, player |
55
+ | `react-hook-form` `^7` | every `*FormField` |
56
+ | `react-redux` `^9`, `@reduxjs/toolkit` `^2` | `DataTable`, filter, gallery, kanban, player, slice helpers |
24
57
  | `@tanstack/react-table` `^9` | `DataTable` |
25
- | `react-use` | assorted hooks |
58
+ | `react-use` `^17` | `useTheme`, `useIsMobile`, other hooks |
26
59
 
27
60
  The tarball is `dist/`, not `lib/`. There is no `postinstall` build.
28
61
 
@@ -39,17 +72,18 @@ In the app's root stylesheet, in this order:
39
72
  ```
40
73
 
41
74
  `styles.css` is the **source** `@theme` layer (colours, `--radius`, `dark`,
42
- fonts) so it runs through *your* Tailwind build. `@source` is required and
43
- silent when missing: Tailwind v4 does not scan `node_modules`, so without it
44
- every utility used *inside* a component is absent and the UI renders unstyled.
75
+ fonts), so it runs through *your* Tailwind build. The `@source` line is
76
+ required, and nothing warns you when it is missing. Tailwind v4 does not scan
77
+ `node_modules`, so without it every utility used *inside* a component is
78
+ missing and the UI renders unstyled.
45
79
 
46
- Import it **explicitly**, nothing injects it: Tailwind v4 builds one `@theme`
47
- from a single CSS graph, and Tailwind CSS IntelliSense reads the tokens from
48
- this entry file to autocomplete them in your components.
80
+ Import it **explicitly**, because nothing injects it. Tailwind v4 builds one
81
+ `@theme` from a single CSS graph, and Tailwind CSS IntelliSense reads the
82
+ tokens from this entry file to autocomplete them in your components.
49
83
 
50
84
  In a Tailwind app, do not also import the compiled `style` condition on
51
- `@nxgt/material` (`dist/material.css`). Take the tokens; let the app produce
52
- the utilities. The compiled file is for apps without Tailwind.
85
+ `@nxgt/material` (`dist/material.css`). Take the tokens and let the app
86
+ generate the utilities. The compiled file is for apps without Tailwind.
53
87
 
54
88
  ### Icons
55
89
 
@@ -62,25 +96,26 @@ import { Icon } from '@nxgt/material/components';
62
96
  // → /assets/icons/sprites/duotone.svg#atom
63
97
  ```
64
98
 
65
- Serve Font Awesome Pro sheets from `public/assets/icons/sprites/<style>.svg`.
66
- This package does **not** ship them: the FA Pro licence covers use in your
67
- own products, not redistribution inside a library. `files` includes
68
- `"!dist/assets"` so a local build cannot leak them into a tarball. A missing
69
- sheet renders as nothing no error, no fallback.
99
+ Serve the Font Awesome Pro sprite sheets from
100
+ `public/assets/icons/sprites/<style>.svg`. This package does **not** ship them:
101
+ the FA Pro licence covers use in your own products, not redistribution inside
102
+ a library. `files` includes `"!dist/assets"` so a local build cannot leak them
103
+ into a tarball. A missing sheet renders nothing, with no error and no fallback.
70
104
 
71
105
  ## Subpaths
72
106
 
73
107
  | Specifier | What is in it |
74
108
  | --- | --- |
75
- | `@nxgt/material` | default barrel + compiled `style` condition (`dist/material.css`, for apps without Tailwind) |
76
- | `@nxgt/material/components` | UI components and `*FormField` wrappers |
77
- | `@nxgt/material/hooks` | `useTheme`, `useIsClient`, `useMobile`, `useSearchParam`, … |
78
- | `@nxgt/material/dnd` | `@dnd-kit` primitives |
79
- | `@nxgt/material/i18n` | `I18nProvider`, `mergeResources`, `createTranslator` |
80
- | `@nxgt/material/lib` | `cn`, `clientOnly`, date/number/upload, `useSliceReducer` |
81
- | `@nxgt/material/models` | status types |
82
- | `@nxgt/material/types` | shared function types |
83
- | `@nxgt/material/styles.css` | source Tailwind v4 tokens **this** is what the app imports |
109
+ | `@nxgt/material` | Everything below in one barrel, plus the compiled `style` condition (`dist/material.css`, for apps without Tailwind) |
110
+ | `@nxgt/material/components` | UI components and `*FormField` wrappers ([Components](#components)) |
111
+ | `@nxgt/material/hooks` | `useTheme`, `useIsClient`, `useIsMobile`, `useLocalizedDate`, … ([hooks](#nxgtmaterialhooks)) |
112
+ | `@nxgt/material/dnd` | `@dnd-kit` primitives ([dnd](#nxgtmaterialdnd)) |
113
+ | `@nxgt/material/i18n` | `I18nProvider`, `mergeResources`, `createTranslator` ([i18n](#nxgtmateriali18n)) |
114
+ | `@nxgt/material/lib` | `cn`, `clientOnly`, date, number and storage utilities, `useSliceReducer` ([lib](#nxgtmateriallib)) |
115
+ | `@nxgt/material/models` | `Status<T>` |
116
+ | `@nxgt/material/types` | shared function types (`PromiseOr`, `FunctionOr`, …) |
117
+ | `@nxgt/material/styles.css` | source Tailwind v4 tokens. **This** is the file the app imports |
118
+
84
119
  ## Usage
85
120
 
86
121
  ### Components
@@ -96,8 +131,9 @@ import { Button, Icon } from '@nxgt/material/components';
96
131
 
97
132
  ### Form fields
98
133
 
99
- Every field has a standalone (`TextField`) and a React Hook Form wrapper
100
- (`TextFormField`) in the same barrel. Wrappers take `control` + `name`.
134
+ Each field has a standalone component (`TextField`) and a React Hook Form
135
+ wrapper (`TextFormField`) in the same barrel. The wrappers take `control` and
136
+ `name`.
101
137
 
102
138
  ```tsx
103
139
  import { TextFormField, Button } from '@nxgt/material/components';
@@ -113,9 +149,11 @@ const form = useForm({ defaultValues: { email: '' } });
113
149
 
114
150
  ### i18n
115
151
 
116
- This package owns the **mechanism**; the app owns resource **content**. The
117
- reserved namespace is `material` `mergeResources` is a shallow per-language
118
- merge, so an app-side `material` key silently shadows every string we ship.
152
+ This package owns the i18n **mechanism**, and the app owns the resource
153
+ **content**. This package's strings live under the `material` namespace.
154
+ `mergeResources` deep-merges your bundle over it per language, so you can
155
+ override one string (`material.command.title`) without losing the rest. Every
156
+ overridden key is reported with `console.warn` outside production.
119
157
 
120
158
  ```ts
121
159
  import {
@@ -140,16 +178,20 @@ export const useTranslation = createTypedTranslation<LocaleKey>(translate);
140
178
  </I18nProvider>
141
179
  ```
142
180
 
143
- Language state lives only in `I18nProvider`. Components in this package fall
144
- back to their English copy if the provider is not mounted. Messages are ICU
145
- MessageFormat; a missing key is returned verbatim.
181
+ Language state lives only in `I18nProvider`. If the provider is not mounted,
182
+ the components in this package fall back to their English copy. Messages use
183
+ ICU MessageFormat, and a missing key is returned as-is.
184
+
185
+ To translate Zod messages as well, add
186
+ `z.config({ customError: zodLocaleError() })`, with `zodLocaleError` from
187
+ `@nxgt/material/lib`.
146
188
 
147
189
  ### App shell
148
190
 
149
- `items` is a prop the shell does not build or filter the menu. `label` is
150
- already translated (`ReactNode`), not a key. `footer: true` pins an entry;
151
- do not rely on "last item". `avatar` and `headerActions` are props: the shell
152
- holds no session.
191
+ `items` is a prop: the shell does not build or filter the menu. Each `label`
192
+ is already translated (a `ReactNode`), not a key. Set `footer: true` to pin an
193
+ entry to the footer; being last in the list does not do it. `avatar` and
194
+ `headerActions` are props too, because the shell holds no session.
153
195
 
154
196
  ```tsx
155
197
  import { ActivityLayout, ActivityContent } from '@nxgt/material/components';
@@ -164,24 +206,355 @@ const items = [
164
206
  </ActivityLayout>
165
207
  ```
166
208
 
167
- The active entry is the **longest** match across the whole menu, segment-aware
168
- (`/admin` does not cover `/administrators`). `AppBreadcrumb` reads
169
- `handle.breadcrumb` off `useMatches()` and needs a data router
209
+ The active entry is the **longest** match across the whole menu. Matching is
210
+ segment-aware, so `/admin` does not cover `/administrators`. `AppBreadcrumb`
211
+ reads `handle.breadcrumb` from `useMatches()`, so it needs a data router
170
212
  (`createMemoryRouter` + `RouterProvider`, not `MemoryRouter`).
171
213
 
214
+ ## Components
215
+
216
+ Everything below is imported from `@nxgt/material/components`. In the
217
+ *Component* column, `·` separates a field from its React Hook Form wrapper.
218
+ The last column gives the matching export in `@nxgt/material-vue`, or `—` when
219
+ Vue has none. "(same parts)" means Vue exports the same sub-components.
220
+
221
+ ### Primitives
222
+
223
+ | Component | What it does | @nxgt/material-vue |
224
+ | --- | --- | --- |
225
+ | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent` | Collapsible sections with a rotating chevron and animated content. | `Accordion` (same parts) |
226
+ | `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent` | A single show / hide region and its trigger. | `Collapsible` (same parts) |
227
+ | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`, `CardContent`, `CardFooter` | Card surface with header, action slot, content and footer. | `Card` (same parts) |
228
+ | `Typography` | Text in the Material type scale (headline, title, body, caption…). | `Typography` |
229
+ | `Label` | Form label with an optional required asterisk. | `Label` |
230
+ | `HelperText` | Hint or error message under a field. | `HelperText` |
231
+ | `ExtendedLabel` | Section title with an animated accent underline and a trailing slot. | `ExtendedLabel` |
232
+ | `Input` | Styled native `<input>`. | `Input` |
233
+ | `Textarea` | Styled native `<textarea>`. | `Textarea` |
234
+ | `InputGroup`, `InputGroupAddon`, `InputGroupInput`, `InputGroupButton`, `InputGroupText`, `InputGroupTextarea` | Input with inline or block addons, buttons and text. | `InputGroup` (same parts) |
235
+ | `Icon` | Font Awesome sprite icon by name and style (duotone by default), labelled with `role="img"`. | `Icon` |
236
+ | `Kbd`, `KbdShortcut` | One keyboard key, or a key combination with platform glyphs. | `Kbd`, `KbdShortcut` |
237
+ | `Separator` | Horizontal or vertical divider. | `Separator` |
238
+ | `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableRow`, `TableHead`, `TableCell`, `TableCaption` | Styled semantic table inside a scrollable wrapper. | `Table` (same parts), `TableEmpty` |
239
+ | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | Tabs whose triggers can take an icon. | `Tabs` (same parts) |
240
+ | `Toggle` | Pressable on / off button: default or outlined, three sizes. | `Toggle` |
241
+ | `ToggleGroup`, `ToggleGroupItem`, `ToggleGroupSeparator` | Single or multiple toggle group sharing variant and size. | `ToggleGroup`, `ToggleGroupItem` |
242
+ | `Activity` | React `<Activity>` switched by a boolean `visible`. | `Activity` |
243
+
244
+ ### Buttons & actions
245
+
246
+ | Component | What it does | @nxgt/material-vue |
247
+ | --- | --- | --- |
248
+ | `Button` | Filled, tonal, outlined, ghost or link button in seven colours, with a loading state. | `Button` |
249
+ | `IconButton` | Icon-only button with an optional tooltip. | `IconButton` |
250
+ | `ButtonGroup` | Joins adjacent buttons into one control. | `ButtonGroup` |
251
+ | `SplitButton` | Main action plus a dropdown menu of secondary actions. | `SplitButton` |
252
+ | `ResponsiveButton` | Shows its label on desktop and collapses to icon-only on mobile. | `ResponsiveButton` |
253
+ | `LinkButton`, `IconLinkButton`, `ResponsiveLinkButton` | The button styles rendered as `react-router` links. | `LinkButton`, `IconLinkButton`, `ResponsiveLinkButton` |
254
+ | `CopyButton`, `CopyValue` | Copies a value to the clipboard and briefly shows a check mark. `CopyValue` displays the value next to the button. | `CopyButton` |
255
+ | `LoadMoreButton` | "Load more" button for cursor pagination; icon-only with a tooltip unless `expanded`. | `LoadMoreButton` |
256
+ | `ScrollToTop` | Floating button that appears past a scroll threshold. | `ScrollToTop` |
257
+ | `ActionCard` | Clickable card with icon, title, description and active indicator. | `ActionCard` |
258
+ | `ThemeToggle` | Light / dark / system switch that stores and applies the choice. | `ThemeToggle` |
259
+ | `LanguageSwitcher` | Globe button with a menu of the `I18nProvider` languages. | `LanguageSwitcher` |
260
+
261
+ ### Form fields
262
+
263
+ | Component | What it does | @nxgt/material-vue |
264
+ | --- | --- | --- |
265
+ | `Field` · `FormField` | Renders the field matching a config `type` (text, select, date, upload, otp…). | `Field` |
266
+ | `TextField` · `TextFormField` | Labelled input with leading / trailing adornments and helper text. | `TextField`, `TextFormField` |
267
+ | `TextareaField` · `TextareaFormField` | Labelled textarea with row sizing and a `maxLength` counter. | `TextareaField`, `TextareaFormField` |
268
+ | `EmailField` · `EmailFormField` | Validates on blur; shows a `mailto:` button once the address is valid. | `EmailField`, `EmailFormField` |
269
+ | `PasswordField` · `PasswordFormField` | Show / hide toggle and an optional strength meter (`scorePassword`). | `PasswordField`, `PasswordFormField` |
270
+ | `NumberField` · `NumberFormField` | Emits `number \| null`, with min / max / step clamping, +/− buttons and a suffix. | `NumberField`, `NumberFormField` |
271
+ | `PercentField` · `PercentFormField` | `NumberField` with a `%` suffix. | `PercentField`, `PercentFormField` |
272
+ | `CurrencyField` · `CurrencyFormField` | Amount formatted to the locale on blur, with a currency suffix (EUR by default). | `CurrencyField`, `CurrencyFormField` |
273
+ | `PhoneField` · `PhoneFormField` | International number with a country picker; emits E.164. | `PhoneField`, `PhoneFormField` |
274
+ | `UrlField` · `UrlFormField` | Validates on blur; open-in-new-tab and copy buttons. | `UrlField`, `UrlFormField` |
275
+ | `SearchField` · `SearchFormField` | Search icon, clear button and spinner, with a debounced `onValueChange`. | `SearchField`, `SearchFormField` |
276
+ | `ColorField` · `ColorFormField` | Hex input with a colour picker in a popover. | `ColorField`, `ColorFormField` |
277
+ | `OtpField` · `OtpFormField` | One-time code input of configurable length. | `OtpField`, `OtpFormField`, `InputOTP` |
278
+ | `InputGroupField` · `InputGroupFormField` | Labelled `InputGroup` with addons and helper text. | `InputGroupField`, `InputGroupFormField` |
279
+ | `InlineEdit` · `InlineEditFormField` | Click-to-edit text with save and cancel. | `InlineEdit` |
280
+ | `CountryField` · `CountryFormField` | Single or multiple country picker with flags. | `CountryField`, `CountryFormField` |
281
+ | `TimezoneField` · `TimezoneFormField` | Searchable IANA timezone picker with localised labels. | `TimezoneField`, `TimezoneFormField` |
282
+ | `RatingField` · `RatingFormField` | Star rating with hover preview, click-to-clear and a read-only mode. | `RatingField`, `RatingFormField` |
283
+ | `Slider` · `SliderFormField` | Labelled single or range slider with an editable value and ticks. | `Slider`, `SliderField`, `SliderFormField` |
284
+ | `Switch` · `SwitchFormField` | Toggle switch, optionally in a labelled, bordered row. | `Switch`, `SwitchField`, `SwitchFormField` |
285
+ | `AttributesField`, `AttributesList`, `AttributeDialog`, `AttributeForm`, `AttributeValuesForm` · `AttributesFormField`, `AttributeValuesFormFieldSet`, `AttributeValueFormField` | Editor for typed custom attributes (string, number, date, select, rich text…). | `AttributesField`, `AttributesList`, `AttributeDialog`, `AttributeForm`, `AttributeValuesForm`, `AttributesFormField`, `AttributeValuesFormFieldSet`, `AttributeValueFormField` |
286
+ | `Contacts`, `ContactItem`, `ContactDialog` · `ContactsFormField` | Typed contact list (phone, email, URL…) with an add / edit dialog. | `Contacts`, `ContactItem`, `ContactDialog`, `ContactsFormField` |
287
+ | `PostalAddress`, `PostalAddressDialog` · `PostalAddressFormField` | Address card with an edit dialog. | `PostalAddress`, `PostalAddressDialog`, `PostalAddressFormField` |
288
+ | `OpeningHours`, `OpeningHoursDialog`, `OpeningHoursItem` · `OpeningHoursFormField` | Weekly opening hours with an add / edit dialog. | `OpeningHours`, `OpeningHoursDialog`, `OpeningHoursItem`, `OpeningHoursFormField` |
289
+
290
+ ### Date & time
291
+
292
+ | Component | What it does | @nxgt/material-vue |
293
+ | --- | --- | --- |
294
+ | `Calendar`, `CalendarDayButton` | Day-picker calendar with a year view, a today shortcut and `disablePast` / `disableFuture`. | `Calendar` (Reka parts) |
295
+ | `DateField` · `DateFormField` | Labelled trigger that opens a calendar popover, with an optional time picker. | `DateField`, `DateFormField` |
296
+ | `DateRangeField` · `DateRangeFormField` | From / to range picked in a popover calendar. | `DateRangeField`, `DateRangeFormField` |
297
+ | `TimeField` · `TimeFormField` | Opens a `TimePicker` in a popover, or in a dialog on mobile. | `TimeField`, `TimeFormField` |
298
+ | `TimePicker` | Hour and minute inputs with AM / PM and auto-advancing focus. | `TimePicker` |
299
+ | `DurationField` · `DurationFormField` | Hours and minutes combined into a total in minutes. | `DurationField`, `DurationFormField` |
300
+
301
+ ### Selection & choice
302
+
303
+ | Component | What it does | @nxgt/material-vue |
304
+ | --- | --- | --- |
305
+ | `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`… | Select primitives with groups, labels and scroll buttons. | `Select` (same parts), `NativeSelect` |
306
+ | `SelectField` · `SelectFormField` | Labelled select built from `options`, with `renderOption`. | `SelectField`, `SelectFormField` |
307
+ | `Combobox`, `ComboboxInput`, `ComboboxContent`, `ComboboxList`, `ComboboxItem`, `ComboboxChips`… | Base UI combobox primitives: input, popup list, groups and chips. | — |
308
+ | `ComboboxField` · `ComboboxFormField` | Labelled, searchable single select over `{ value, label }` options. | `ComboboxField`, `ComboboxFormField` |
309
+ | `Autocomplete`, `AutocompleteChips` · `AutocompleteFormField` | Single or multiple (chips) autocomplete with free-solo values and a query callback. | `Autocomplete`, `AutocompleteFormField` |
310
+ | `Checkbox` · `CheckboxFormField` | Checkbox with label, helper text and an indeterminate state. | `Checkbox` |
311
+ | `CheckboxGroup` · `CheckboxGroupFormField` | Checkbox list built from `options`, valued as `string[]`. | `CheckboxGroup`, `CheckboxGroupFormField` |
312
+ | `RadioGroup`, `RadioGroupItem` · `RadioGroupFormField` | Labelled radio group built from `options`, horizontal or vertical. | `RadioGroupField`, `RadioGroup`, `RadioGroupFormField` |
313
+ | `CheckCardField` · `CheckCardFormField` | A yes / no choice shown as two selectable cards. | `CheckCardField`, `CheckCardFormField` |
314
+ | `SelectCardField` · `SelectCardFormField` | Single or multiple choice shown as `ActionCard`s. | `SelectCardField`, `SelectCardFormField` |
315
+ | `SelectChipField` · `SelectChipFormField` | Single or multiple choice shown as toggleable chips. | `SelectChipField`, `SelectChipFormField` |
316
+ | `TransferList` · `TransferListFormField` | Two-pane picker with search, drag-and-drop, reordering and a `max`. | `TransferList`, `TransferListFormField` |
317
+ | `SortableList` · `SortableListFormField` | Searchable list of strings you reorder by dragging or with the keyboard. | `SortableList`, `SortableListFormField` |
318
+ | `Switcher` | Dropdown for switching the active workspace or account, with an avatar. | `Switcher` |
319
+
320
+ ### Data display
321
+
322
+ | Component | What it does | @nxgt/material-vue |
323
+ | --- | --- | --- |
324
+ | `Avatar`, `AvatarImage`, `AvatarFallback`, `AvatarBase` | Image with a fallback and an optional online / offline dot. | `Avatar`, `AvatarImage`, `AvatarFallback` |
325
+ | `AvatarGroup` | Overlapping avatars with a `+N` overflow. | `AvatarGroup` |
326
+ | `Badge` | Pill label: default, secondary, error, success, info, warning, outlined. | `Badge` |
327
+ | `CountBadge` | Count in the corner of its children, capped at `99+` and hidden at 0. | `CountBadge` |
328
+ | `Chip` | Pill with leading, avatar and trailing slots, an active state and a dismiss button. | `Chip` |
329
+ | `StatCard` | KPI tile with value, hint, icon, toned delta and a loading skeleton. | `StatCard` |
330
+ | `StatusIndicator` | Coloured status dot with an optional pulse and label. | `StatusIndicator` |
331
+ | `SummaryData` | Label / value pairs as a grid or inline; hides empty values. | `SummaryData` |
332
+ | `Description` | Read-only label / value pair that renders nothing when empty. | `Description` |
333
+ | `EntityHeader` | Page header with icon, title, status, metadata and actions. | `EntityHeader` |
334
+ | `ListTile` | Row with leading / trailing slots, title and subtitle; becomes a link or button when interactive. | `ListTile` |
335
+ | `AccordionCard` | Renders `items` (title, content) as a styled accordion. | `AccordionCard` |
336
+ | `Timeline`, `TimelineItem`, `RelativeTime` | Vertical event timeline with toned icons and relative times. | `Timeline`, `TimelineItem`, `RelativeTime` |
337
+ | `EventChip` | Calendar event pill with time, all-day and continuation edges. | `EventChip` |
338
+ | `CodeBlock`, `CodeDiff`, `JsonViewer` | Highlighted code with line numbers and copy, a Monaco diff, and a collapsible JSON tree. | `CodeBlock`, `CodeDiff`, `JsonViewer` |
339
+ | `QRCode` | QR code on a padded tile, with an optional centred logo. | `QRCode` |
340
+ | `Barecode` | 1D barcode. | `Barecode` |
341
+
342
+ ### Data table, filter & lists
343
+
344
+ | Component | What it does | @nxgt/material-vue |
345
+ | --- | --- | --- |
346
+ | `DataTable`, `DataTableColumnHeader`, `DataTablePagination`, `DataTableViewOptions` | TanStack Table v9 with sorting, filtering, selection, expandable rows, pagination or load-more, and column visibility. | `DataTable`, `DataTableColumnHeader`, `DataTablePagination`, `DataTableViewOptions` |
347
+ | `Filter`, `FilterProvider`, `FilterContent`, `FilterChips`, `FilterPresets` · `FilterFormField` | Schema-driven filters with chips, presets, validation, and URL or storage persistence. | `Filter`, `FilterProvider`, `FilterContent`, `FilterChips`, `FilterPresets`, `FilterFormField` |
348
+ | `FilterInline`, `FilterPopover`, `FilterDialog`, `FilterSheet`, `FilterDrawer` | The filter panel shown inline, or in a popover, dialog, sheet or drawer. | `FilterInline`, `FilterPopover`, `FilterDialog`, `FilterSheet`, `FilterDrawer` |
349
+ | `Pagination`, `PaginationContent`, `PaginationItem`, `PaginationLink`, `PaginationBar`… | Pagination primitives. `PaginationBar` adds a sibling window with ellipses. | `Pagination` (parts), `PaginationFirst`, `PaginationLast` |
350
+ | `Tree`, `TreeItem`, `useTree` | Expandable tree with none, single or multiple selection and keyboard navigation. | `Tree`, `TreeItem`, `useTree` |
351
+ | `DndProvider`, `Draggable`, `Droppable`, `Sortable`, `DragHandle`… | Drag-and-drop primitives; see [dnd](#nxgtmaterialdnd). | `DndProvider`, `Draggable`, `Droppable`, `Sortable`, `DragHandle` |
352
+
353
+ ### Navigation & layout
354
+
355
+ | Component | What it does | @nxgt/material-vue |
356
+ | --- | --- | --- |
357
+ | `ActivityLayout`, `ActivityContent`, `AppSidebar`, `AppHeader`, `AppBreadcrumb` | App shell: sidebar built from `items`, a header with a mobile sheet, route breadcrumbs and a titled page. See [App shell](#app-shell). | `ActivityLayout`, `ActivityContent`, `AppSidebar`, `AppHeader`, `AppBreadcrumb` |
358
+ | `Sidebar`, `SidebarHeader`, `SidebarContent`, `SidebarFooter`, `SidebarMenuItem`, `SidebarMenuLabel` | Collapsible navigation sidebar with icons, tooltips and active items. | `Sidebar` (same parts) |
359
+ | `TopAppBar`, `BottomAppBar` | Top bar surface, and a bottom bar of icon actions with an active state. | `TopAppBar`, `BottomAppBar` |
360
+ | `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbPage`, `BreadcrumbSeparator`, `BreadcrumbEllipsis` | Breadcrumb primitives. | `Breadcrumb` (same parts) |
361
+ | `ListDetailsLayout`, `PaneLayout`, `ThreadLayout` | List / details that collapses to one view on mobile, a side pane that becomes a sheet, and a centred column. | `ListDetailsLayout`, `PaneLayout`, `ThreadLayout` |
362
+ | `ResponsiveGrid` | Container-query grid that grows from 1 to 5 columns. | `ResponsiveGrid` |
363
+ | `ResizableGroup`, `ResizablePanel`, `ResizableSeparator` | Resizable panels with an optional grip handle. | `ResizablePanelGroup`, `ResizablePanel`, `ResizableHandle` |
364
+ | `Stepper`, `StepperIndicator` | Clickable horizontal or vertical steps with status and progress; compact on mobile. | `Stepper` |
365
+ | `Background` | Full-viewport gradient with blurred colour blobs; centres its children. | `Background` |
366
+ | `NavigationRail` | Placeholder: renders an empty rail container and takes no props yet. | `NavigationRail` |
367
+
368
+ ### Overlays & menus
369
+
370
+ | Component | What it does | @nxgt/material-vue |
371
+ | --- | --- | --- |
372
+ | `Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogTitle`, `DialogDescription`, `DialogFooter`, `DialogClose`… | Modal dialog parts with an icon close button. | `Dialog` (same parts), `DialogScrollContent` |
373
+ | `Sheet`, `SheetTrigger`, `SheetContent`, `SheetHeader`, `SheetTitle`, `SheetFooter`… | Panel that slides in from any edge. | `Sheet` (same parts) |
374
+ | `Drawer`, `DrawerTrigger`, `DrawerContent`, `DrawerHeader`, `DrawerTitle`, `DrawerFooter`… | Swipeable bottom or side drawer (Vaul). | `Drawer` (same parts) |
375
+ | `Popover`, `PopoverTrigger`, `PopoverContent`, `PopoverAnchor` | Floating panel anchored to a trigger. | `Popover` (same parts) |
376
+ | `HoverCard`, `HoverCardTrigger`, `HoverCardContent` | Card shown on pointer hover. | `HoverCard` (same parts) |
377
+ | `Tooltip`, `TooltipProvider`, `TooltipTrigger`, `TooltipContent` | Tooltip. The `Tooltip` shorthand wraps its children and shows `content`. | `Tooltip` (same parts) |
378
+ | `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, `DropdownMenuSub`… | Menu with checkbox and radio items, submenus, shortcuts and an error item. | `DropdownMenu` (same parts) |
379
+ | `ContextMenu`, `ContextMenuTrigger`, `ContextMenuContent`, `ContextMenuItem`… | Right-click menu with the same kinds of items. | `ContextMenu` (same parts) |
380
+ | `Menubar`, `MenubarMenu`, `MenubarTrigger`, `MenubarContent`, `MenubarItem`… | Desktop-style menubar. | `Menubar` (same parts) |
381
+ | `Command`, `CommandDialog`, `CommandInput`, `CommandList`, `CommandItem`… | Command palette with filtering, groups and shortcuts. `CommandDialog` shows it in a modal. | `Command`, `CommandDialog` (same parts) |
382
+
383
+ ### Feedback & status
384
+
385
+ | Component | What it does | @nxgt/material-vue |
386
+ | --- | --- | --- |
387
+ | `Alert` | Callout with icon, title and description in seven tones. | `Alert` |
388
+ | `Banner` | Dismissible info / success / warning / error banner with an action. | `Banner` |
389
+ | `AlertDialog`, `AlertDialogContent`, `AlertDialogAction`, `AlertDialogCancel`… | Parts for a blocking confirmation dialog. | `AlertDialog` (same parts) |
390
+ | `ConfirmDialog`, `ConfirmationDialog`, `CustomAlertDialog` | Confirm / cancel dialog with async loading. `ConfirmationDialog` adds a trigger and an optional Zod form. | `ConfirmDialog`, `ConfirmationDialog`, `CustomAlertDialog` |
391
+ | `CustomDialog` | Ready-made dialog with trigger, icon, title, body and footer. | `CustomDialog` |
392
+ | `EmptyState` | Icon, title, description and action, with not-found, unauthorized, generic and empty presets. | `EmptyState` |
393
+ | `Progress`, `CircularProgress`, `ProgressField` | Linear bar, circular ring with a label, and a labelled progress field. | `Progress`, `CircularProgress`, `ProgressField` |
394
+ | `Spinner` | Loading spinner in several styles and colours. | `Spinner` |
395
+ | `Toaster`, `toast`, `toasts`, `Toast` | Sonner toasts with icon and action. `toasts` provides `success` / `error` / `warning` / `info` / `loading` presets. | `Toaster`, `toast`, `toasts`, `Toast` |
396
+ | `Skeleton`, `SkeletonAvatar`, `SkeletonCard`, `SkeletonText`, `SkeletonTable` | Loading placeholder and ready-made shapes. | `Skeleton` (same parts) |
397
+ | `HydrateLoading` | Full-area spinner shown while hydrating. | `HydrateLoading` |
398
+ | `Notifications` | Notification centre as a bell popover with an unread count, or as an inline panel, with filters and read / dismiss. | `Notifications` |
399
+
400
+ ### Media & files
401
+
402
+ | Component | What it does | @nxgt/material-vue |
403
+ | --- | --- | --- |
404
+ | `S3UploadField`, `NetworkUploadField` · `S3UploadFormField`, `NetworkUploadFormField` | Drag-and-drop uploader with progress, retry, previews and size / count limits. The S3 variant uploads through a presigned PUT. | `S3UploadField`, `NetworkUploadField`, `S3UploadFormField`, `NetworkUploadFormField` |
405
+ | `UploadField` · `UploadFormField` | Labelled file input showing the chosen file's name. | `UploadField`, `UploadFormField` |
406
+ | `FileList`, `FileListField`, `FileListS3Field` · `FileListFormField`, `FileListS3FormField` | File list with type icons, size, download and remove. The field variants upload with progress. | `FileList`, `FileListField`, `FileListS3Field`, `FileListFormField`, `FileListS3FormField` |
407
+ | `FilesFormField` | Free-solo autocomplete of file URLs, with image thumbnails. | — |
408
+ | `ImageField`, `ImageS3Field` · `ImageFormField`, `ImageS3FormField` | Single image upload with preview and progress. | `ImageField`, `ImageS3Field`, `ImageFormField`, `ImageS3FormField` |
409
+ | `ImageCropper`, `ImageCropperDialog` | Crop with zoom, rotation, aspect presets and a round shape; outputs a `File`. | `ImageCropper`, `ImageCropperDialog` |
410
+ | `Gallery`, `LightboxGallery`, `MiniGallery` | Image / video gallery (grid, masonry, justified, carousel…) with a lightbox and selection. | `Gallery`, `LightboxGallery`, `MiniGallery` |
411
+ | `Carousel`, `CarouselContent`, `CarouselItem`, `CarouselPrevious`, `CarouselNext` | Embla carousel with keyboard navigation. | `Carousel` (same parts) |
412
+ | `PdfReader`, `PdfViewer`, `PdfToolbar`, `PdfSidebar`, `PdfPageList`… | Browser-only PDF reader with zoom, rotation, search, thumbnails, outline and print. | `PdfReader`, `PdfViewer` (same parts) |
413
+ | `StxPlayer`, `StxPlayerProvider`, `StxPlayerMini`, `StxPlayerQueue`… | Video / audio player with a queue, captions, playback rate, PiP, and full or mini layouts. | `StxPlayer` (same parts) |
414
+ | `VideoPlayer` | Deprecated alias of `StxPlayer`. | `VideoPlayer` |
415
+ | `PrintHeader`, `PrintSection`, `PrintTable`, `PrintTotals`, `PrintCard`… | `@react-pdf/renderer` building blocks for printable PDF documents, with a shared `tw`. | `PrintHeader`, `PrintSection`, `PrintTable`, `PrintTotals`, `PrintCard` |
416
+
417
+ ### Rich widgets
418
+
419
+ | Component | What it does | @nxgt/material-vue |
420
+ | --- | --- | --- |
421
+ | `ChatThread`, `ChatMessageList`, `ChatMessage`, `ChatComposer`, `ChatConversationItem`… | Chat kit: auto-scrolling list, streaming and tool messages, a composer with attachments, and markdown. | `ChatThread`, `ChatMessageList`, `ChatMessage`, `ChatComposer` (same parts) |
422
+ | `Kanban` | Board with columns, swimlanes, WIP limits, search, and drag-and-drop cards and columns. | `Kanban` |
423
+ | `EventCalendar` | Month / week / day / agenda calendar with drag, resize and slot creation. | `EventCalendar` |
424
+ | `AreaChart`, `BarChart`, `LineChart`, `PieChart`, `RadarChart`, `ScatterChart`, `FunnelChart`, `HeatmapChart` | Nivo charts themed from the CSS tokens, with loading and empty states (`ChartContainer`). | same names |
425
+ | `RichTextEditor` · `RichTextEditorFormField` | Tiptap editor with toolbar, bubble menu, slash commands, mentions, tables, emoji and images. | `RichTextEditor`, `RichTextEditorFormField` |
426
+ | `CodeEditor` · `CodeEditorFormField` | Monaco editor field with a label and a language switch. | `CodeEditor` |
427
+ | `Quiz`, `QuestionCard` | Multi-step quiz with choice, boolean and text questions, answer reveal and scoring. | `Quiz`, `QuestionCard` |
428
+
429
+ `@nxgt/material-vue` also has components with no React counterpart here:
430
+ `Camera`, `ScrollArea`, `NativeSelect` and the `InputOTP` primitives.
431
+
432
+ ## Helpers
433
+
434
+ Everything below is also re-exported from the root `@nxgt/material` barrel.
435
+ The Vue sister, `@nxgt/material-vue`, uses the **same names** for the plain
436
+ functions; the differences are listed under each table.
437
+
438
+ ### `@nxgt/material/lib`
439
+
440
+ | Export | What it does |
441
+ | --- | --- |
442
+ | `cn(...classes)` | `clsx` + `tailwind-merge`: joins conditional classes, last Tailwind utility wins. |
443
+ | `clientOnly(Component)` | Wraps a component so it renders nothing on the server and mounts only on the client. |
444
+ | `renderSlot(slot, context)` | Resolves a `ContentSlot` — a `ReactNode` or a `(context) => ReactNode`. |
445
+ | `DATE_UTILS` | `format(iso, 'PP', locale?)` (`'-'` when empty), `parseFromTimestring('HH:mm')`, `parseFromISOString`, `formatDateRangeFilter`. |
446
+ | `formatDateRangeFilter(range)` | Turns a day-picker range into `{ from, to }` ISO strings at start / end of day. |
447
+ | `formatNumber(value, { language?, ...Intl.NumberFormatOptions })` | `Intl.NumberFormat` with a language shortcut (`'en-US'` by default). |
448
+ | `ifEmptyString(value, fallback)`, `ifEmptyList(value, fallback)` | Returns the fallback when the string / array is null or empty. |
449
+ | `objectToFormData(obj)`, `formDataToObject(formData)` | Converts a flat object to `FormData` (skipping nullish values) and back. |
450
+ | `convertToFormData(files, fieldName = 'files')` | Appends one file or a list of files to a new `FormData`. |
451
+ | `uniqueArray(array)` | De-duplicates an array. |
452
+ | `findByKey(array, key, value)`, `findByKeyIn(array, key, values)` | Finds the first item / every item whose `key` matches. |
453
+ | `useFindByKey(items, key)`, `useFindById(items)` | Memoised `findByKey` / `findByKeyIn` bound to a list. |
454
+ | `pick(obj, keys)`, `omit(obj, keys)` | Typed pick / omit for one key or a list. |
455
+ | `cleanObject(obj, cleanNulls?)` | Drops `undefined` (and optionally `null`) properties. |
456
+ | `cast<T>(value)`, `castAsync<T>(promise)` | Type-only casts. |
457
+ | `delay(ms)` | Promise that resolves after `ms`. |
458
+ | `safeGetItem(key, default)`, `safeSetItem(key, value)`, `safeRemoveItem(key)` | JSON `localStorage` access that never throws (SSR, private mode). Grouped as `LOCALE_STORE_UTILS`. |
459
+ | `enumToKebabCase(value)`, `joinTruthyParts(parts, sep = ', ')` | `SOME_ENUM` → `some-enum`; joins the truthy parts only. Grouped as `STRING_UTILS`. |
460
+ | `STYLES_UTILS.colorFromVariable(name, alpha?)` | `rgb(from var(--name) r g b / alpha)` for a theme token. |
461
+ | `searchParams(request)`, `searchParam(request, key)` | Reads the query string of a Fetch `Request` (loaders, actions). |
462
+ | `buildFieldOptions(values, { valueKey, label })` | Maps records to `{ value, label }` options by path or label function. |
463
+ | `zodLocaleError()` | Zod v4 `customError` that translates issues through `material.zod.*`: `z.config({ customError: zodLocaleError() })`. |
464
+ | `parsedType(data)` | Runtime type name used for Zod's `{received}` placeholder. |
465
+
466
+ **Redux Toolkit helpers** (same subpath) — the state pattern behind filter,
467
+ gallery, kanban and player:
468
+
469
+ | Export | What it does |
470
+ | --- | --- |
471
+ | `useSliceReducer(slice, initialState?)` | Runs an RTK slice as *local* component state via `useReducer`; returns `[state, boundActions]`. |
472
+ | `resolveSliceState(base, overrides?)` | Merges a slice's initial state with partial overrides. |
473
+ | `useSliceSelector(state, selector)` | Memoised selector over local slice state. |
474
+ | `useActions(actionCreators, deps?)` | Binds action creators to the store `dispatch` (`react-redux`). |
475
+ | `createStatusSlice({ name, … })` | Slice over a `Status<T>` with `reset`, `start`, `success(data)` and `error(message)`. |
476
+ | `createStatusesSlice({ name, initialState, … })` | Same for a record of named `Status` entries, addressed by path. |
477
+ | `createStepFormSlice({ name, initialState? })` | Multi-step form: `step`, `values`, `isDirty`, with `updateStep`, `updateFormValues`, `reset`. |
478
+ | `createSliceWithFilter({ name, initialState, … })` | Adds `updateFilter` and `resetFilter` for a `{ filter, sort }` state. |
479
+ | `createAsyncSlice` | `buildCreateSlice` with the `asyncThunk` creator enabled. |
480
+
481
+ Vue: the Redux helpers are Pinia store factories — `defineLocalStore`
482
+ (per-instance store, like `useSliceReducer`), `defineStatusStore`,
483
+ `defineStatusesStore`, `defineStepFormStore`, `defineFilterStore`,
484
+ `resolveStoreState`, plus `runStatus`. `createAsyncSlice`, `useActions` and
485
+ `useSliceSelector` have no Vue counterpart.
486
+
487
+ ### `@nxgt/material/hooks`
488
+
489
+ | Export | What it does |
490
+ | --- | --- |
491
+ | `useTheme()` | `{ theme, preference, updateTheme }`: light / dark / system, stored in `localStorage`, applied as the `dark` class. |
492
+ | `resolveTheme(stored, prefersDark)`, `applyResolvedTheme(theme)` | The pure theme logic behind `useTheme`, usable in an inline boot script. |
493
+ | `useIsClient()` | `false` on the server and first render, `true` after mount — a hydration guard. |
494
+ | `useIsMobile()` | `true` below 768 px. |
495
+ | `useLocalizedDate()` | `{ language, locale, format }`: `DATE_UTILS.format` bound to the `I18nProvider` language. |
496
+ | `useNumberFormatter()` | `{ formatNumber, formatCurrency }` bound to the `I18nProvider` language. |
497
+ | `useOptions(items, { value, label, filter?, expanded? })` | Memoised `{ value, label }` options for selects; `buildOptions` is the non-hook version. |
498
+ | `extractNode(connection)` | Unwraps a GraphQL `{ edges: [{ node }] }` connection into an array. |
499
+ | `useSearchParam(name)` | One query-string value from the router (`react-router`). |
500
+ | `useNavigationHandler(to, options?)`, `useNavigationHandlers(routes)` | Stable click handlers that navigate (`react-router`). |
501
+ | `useAllowedTransitions(status, transitions, configs)` | Status-machine actions (icon, colour, label, dialog copy) allowed from the current status. |
502
+
503
+ Vue: `@nxgt/material-vue/composables`, same names.
504
+
505
+ ### `@nxgt/material/i18n`
506
+
507
+ | Export | What it does |
508
+ | --- | --- |
509
+ | `I18nProvider` | Holds the current language (props `defaultLanguage`, `initialLanguage` for SSR, `storageKey`, `supportedLanguages`, `onLanguageChange`). |
510
+ | `useI18nContext()`, `useOptionalI18nContext()` | Reads `{ language, setLanguage, … }`; the first throws outside the provider, the second returns `null`. |
511
+ | `mergeResources(base, app)` | Deep, per-language merge (`lodash.merge`) of the package bundle and yours; your leaf keys win, and each override is warned about outside production. |
512
+ | `resources` | The package's `en` / `fr` bundle, namespace `material`. |
513
+ | `createTranslator(resources, getLanguage?)` | ICU MessageFormat translator; a missing key comes back verbatim. |
514
+ | `createTypedTranslation(translate)` | Turns a translator into a typed `useTranslation()` hook. |
515
+ | `translate`, `useMaterialTranslation()` | Translator / hook bound to the package's own `material.*` keys. |
516
+ | `getLanguage()` | Language read from `localStorage`, else `FALLBACK_LANGUAGE`. |
517
+ | `LANGUAGE_KEY`, `FALLBACK_LANGUAGE`, `SUPPORTED_LANGUAGES` | Storage key (`'language'`), fallback and supported languages (`en`, `fr`). |
518
+
519
+ Types: `FlatObject`, `Path`, `Language`, `MaterialLocaleKey`, `Translate`,
520
+ `TranslationContext`, `ResourceBundle`. Vue adds `provideI18n()` for apps that
521
+ provide the context from `setup()` instead of the component.
522
+
523
+ ### `@nxgt/material/models`, `/types`
524
+
525
+ | Export | What it does |
526
+ | --- | --- |
527
+ | `Status<T>` | `{ data?, status: 'idle' \| 'loading' \| 'success' \| 'error', error? }` — the shape the status slices manage. |
528
+ | `PromiseOr<T>`, `FunctionOr<T, R>`, `AsyncFunctionOr<T, R>` | A value or a promise; a value or a function computing it. |
529
+ | `Predicate<T>`, `AsyncPredicate<T>` | `(arg) => boolean` and its async form. |
530
+
531
+ ### `@nxgt/material/dnd`
532
+
533
+ The drag-and-drop layer on `@dnd-kit`, also in `/components`:
534
+
535
+ | Export | What it does |
536
+ | --- | --- |
537
+ | `DndProvider`, `DndOverlay` | The `DndContext` (sensors, collision detection) and the floating drag preview. |
538
+ | `Draggable`, `Droppable`, `DraggableDroppable`, `DragHandle` | Primitives for free drag, drop zones, nested boards and handles. |
539
+ | `Sortable`, `SortableProvider` | A sortable item and its list context. |
540
+ | `useDndItem`, `useDndActiveId` | The item's drag state; the id being dragged. |
541
+ | `insertIds`, `arrayMove`, `composeRefs` | Pure list and ref helpers. |
542
+ | `closestCenter`, `pointerWithin`, `rectIntersection`, `verticalListSortingStrategy`, `horizontalListSortingStrategy`, `CSS` | Re-exported from `@dnd-kit`. |
543
+
172
544
  ## SSR
173
545
 
174
546
  An SSR app that bundles this package (`ssr.noExternal: ['@nxgt/material']`)
175
- evaluates every module the barrels reach at **boot**, whether or not a page
176
- renders the component.
547
+ runs every module the barrels reach when the server **boots**, whether or not
548
+ a page renders the component.
177
549
 
178
- - **Nothing browser-only at module scope.** `pdfjs-dist` runs `new DOMMatrix()`
179
- at import. Reach it through `import()` and wrap the component with
180
- `clientOnly` from `@nxgt/material/lib`. A `typeof window` guard around a
181
- statement does nothing when the *import* is the side effect.
550
+ - **Nothing browser-only at module scope.** `pdfjs-dist` runs
551
+ `new DOMMatrix()` at import. Load it with `import()` and wrap the component
552
+ in `clientOnly` from `@nxgt/material/lib`. A `typeof window` guard around a
553
+ statement does nothing when the *import* itself is the side effect.
182
554
  - **Never `<svg><title>`.** React 19 hoists `<title>` as document metadata:
183
- server emits `<title></title>`, client renders the text, hydration discards
184
- the whole tree. Use `role="img"` + `aria-label` (`Icon` already does).
555
+ the server emits `<title></title>` while the client renders the text, and
556
+ the hydration mismatch makes React discard the whole tree. Use
557
+ `role="img"` + `aria-label` instead (`Icon` already does).
185
558
 
186
559
  ## Traps
187
560
 
@@ -189,7 +562,7 @@ renders the component.
189
562
  | --- | --- | --- |
190
563
  | Components render unstyled, no console error | Tailwind did not scan the package | `@source "../node_modules/@nxgt/material/"` |
191
564
  | `<Icon>` is blank | sprite sheet not served | `public/assets/icons/sprites/<style>.svg` |
192
- | Package strings vanish after adding i18n | app declared `material` | never a top-level `material` key in app resources |
565
+ | Package strings change after adding i18n | your resources define keys under `material` | keep app strings in your own namespaces; `console.warn` lists each override in development |
193
566
  | Hydration mismatch on every page | `<svg><title>` | `role="img"` + `aria-label` |
194
567
  | `ReferenceError: DOMMatrix` at server boot | static `react-pdf` import | `import()` + `clientOnly` |
195
568
 
@@ -201,3 +574,8 @@ bun run build # what consumers read
201
574
  bun run storybook
202
575
  bun run test
203
576
  ```
577
+
578
+ ## License
579
+
580
+ [MIT](https://opensource.org/license/mit). The `LICENSE` file ships in the
581
+ package.