@nxgt/material 1.0.0 → 1.0.2
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/README.md +160 -68
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,107 +1,199 @@
|
|
|
1
1
|
# @nxgt/material
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
helpers and the `Icon` component.
|
|
3
|
+
React component library: primitives, form fields, layout shell, `DataTable`,
|
|
4
|
+
drag-and-drop, i18n, and `Icon`. Tailwind CSS v4 + Base UI.
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
## Install
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
```bash
|
|
9
|
+
bun add @nxgt/material
|
|
10
|
+
```
|
|
10
11
|
|
|
11
|
-
|
|
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:
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
```bash
|
|
16
|
+
bun add react react-dom react-hook-form react-redux @reduxjs/toolkit @tanstack/react-table react-use
|
|
17
|
+
```
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
| Peer | Used by |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `react`, `react-dom` `^19` | everything |
|
|
22
|
+
| `react-hook-form` | every `*FormField` |
|
|
23
|
+
| `react-redux`, `@reduxjs/toolkit` | `DataTable`, filter, gallery, kanban, player |
|
|
24
|
+
| `@tanstack/react-table` `^9` | `DataTable` |
|
|
25
|
+
| `react-use` | assorted hooks |
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
// package.json
|
|
20
|
-
"dependencies": {
|
|
21
|
-
"@nxgt/material": "…"
|
|
22
|
-
}
|
|
23
|
-
```
|
|
27
|
+
The tarball is `dist/`, not `lib/`. There is no `postinstall` build.
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
## Setup
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
### Styles
|
|
32
|
+
|
|
33
|
+
In the app's root stylesheet, in this order:
|
|
29
34
|
|
|
30
35
|
```css
|
|
31
36
|
@import "tailwindcss";
|
|
32
|
-
|
|
33
37
|
@import "@nxgt/material/styles.css";
|
|
34
|
-
|
|
35
38
|
@source "../node_modules/@nxgt/material/";
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- **`@source "../node_modules/@nxgt/material/"`** is not optional and its
|
|
43
|
-
absence does not error. Tailwind v4 generates only the utilities it can see in
|
|
44
|
-
the files it scans, and by default it does not scan inside `node_modules`.
|
|
45
|
-
Without this line, every utility class used *inside* a `@nxgt/material`
|
|
46
|
-
component is absent from the output and the components render unstyled — a
|
|
47
|
-
blank-looking app with no message in any console.
|
|
41
|
+
`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.
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
`
|
|
51
|
-
the tokens and let their own Tailwind build produce the utilities.
|
|
46
|
+
Do not import the compiled `style` condition on `@nxgt/material`
|
|
47
|
+
(`dist/material.css`). Take the tokens; let the app produce the utilities.
|
|
52
48
|
|
|
53
|
-
###
|
|
49
|
+
### Icons
|
|
54
50
|
|
|
55
|
-
`Icon`
|
|
51
|
+
`Icon` points at a **root-relative** URL on *your* origin:
|
|
56
52
|
|
|
57
53
|
```tsx
|
|
58
|
-
|
|
54
|
+
import { Icon } from '@nxgt/material/components';
|
|
55
|
+
|
|
56
|
+
<Icon name="atom" type="duotone" />
|
|
57
|
+
// → /assets/icons/sprites/duotone.svg#atom
|
|
59
58
|
```
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
package
|
|
60
|
+
Serve Font Awesome Pro sheets from `public/assets/icons/sprites/<style>.svg`.
|
|
61
|
+
This package does **not** ship them: the FA Pro licence covers use in your
|
|
62
|
+
own products, not redistribution inside a library. `files` includes
|
|
63
|
+
`"!dist/assets"` so a local build cannot leak them into a tarball. A missing
|
|
64
|
+
sheet renders as nothing — no error, no fallback.
|
|
65
|
+
|
|
66
|
+
## Subpaths
|
|
67
|
+
|
|
68
|
+
| Specifier | What is in it |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `@nxgt/material` | default barrel + compiled `style` condition (unused; import `styles.css`) |
|
|
71
|
+
| `@nxgt/material/components` | UI components and `*FormField` wrappers |
|
|
72
|
+
| `@nxgt/material/hooks` | `useTheme`, `useIsClient`, `useMobile`, `useSearchParam`, … |
|
|
73
|
+
| `@nxgt/material/dnd` | `@dnd-kit` primitives |
|
|
74
|
+
| `@nxgt/material/i18n` | `I18nProvider`, `mergeResources`, `createTranslator` |
|
|
75
|
+
| `@nxgt/material/lib` | `cn`, `clientOnly`, date/number/upload, `useSliceReducer` |
|
|
76
|
+
| `@nxgt/material/models` | status types |
|
|
77
|
+
| `@nxgt/material/types` | shared function types |
|
|
78
|
+
| `@nxgt/material/styles.css` | source Tailwind v4 tokens — **this** is what the app imports |
|
|
63
79
|
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
### Components
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { Button, Icon } from '@nxgt/material/components';
|
|
86
|
+
|
|
87
|
+
<Button variant="filled" color="primary">
|
|
88
|
+
<Icon name="plus" />
|
|
89
|
+
Add
|
|
90
|
+
</Button>
|
|
64
91
|
```
|
|
65
|
-
|
|
92
|
+
|
|
93
|
+
### Form fields
|
|
94
|
+
|
|
95
|
+
Every field has a standalone (`TextField`) and a React Hook Form wrapper
|
|
96
|
+
(`TextFormField`) in the same barrel. Wrappers take `control` + `name`.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { TextFormField, Button } from '@nxgt/material/components';
|
|
100
|
+
import { useForm } from 'react-hook-form';
|
|
101
|
+
|
|
102
|
+
const form = useForm({ defaultValues: { email: '' } });
|
|
103
|
+
|
|
104
|
+
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
105
|
+
<TextFormField control={form.control} name="email" label="Email" />
|
|
106
|
+
<Button type="submit">Save</Button>
|
|
107
|
+
</form>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### i18n
|
|
111
|
+
|
|
112
|
+
This package owns the **mechanism**; the app owns resource **content**. The
|
|
113
|
+
reserved namespace is `material` — `mergeResources` is a shallow per-language
|
|
114
|
+
merge, so an app-side `material` key silently shadows every string we ship.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import {
|
|
118
|
+
I18nProvider,
|
|
119
|
+
createTranslator,
|
|
120
|
+
createTypedTranslation,
|
|
121
|
+
mergeResources,
|
|
122
|
+
resources as materialResources,
|
|
123
|
+
type FlatObject,
|
|
124
|
+
} from '@nxgt/material/i18n';
|
|
125
|
+
import { resources as appResources } from './resources';
|
|
126
|
+
|
|
127
|
+
export const resources = mergeResources(materialResources, appResources);
|
|
128
|
+
export type LocaleKey = keyof FlatObject<typeof resources.en, string>;
|
|
129
|
+
export const translate = createTranslator<LocaleKey>(resources);
|
|
130
|
+
export const useTranslation = createTypedTranslation<LocaleKey>(translate);
|
|
66
131
|
```
|
|
67
132
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
133
|
+
```tsx
|
|
134
|
+
<I18nProvider defaultLanguage="en">
|
|
135
|
+
<App />
|
|
136
|
+
</I18nProvider>
|
|
137
|
+
```
|
|
73
138
|
|
|
74
|
-
|
|
75
|
-
|
|
139
|
+
Language state lives only in `I18nProvider`. Components in this package fall
|
|
140
|
+
back to their English copy if the provider is not mounted. Messages are ICU
|
|
141
|
+
MessageFormat; a missing key is returned verbatim.
|
|
76
142
|
|
|
77
|
-
|
|
78
|
-
is fine.** A private application repository holding the sprites it serves is
|
|
79
|
-
exactly the "own products" case the licence covers; every consumer in the parc
|
|
80
|
-
does it. The line is redistribution through a package, not storage.
|
|
143
|
+
### App shell
|
|
81
144
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`notdog`, `slab-press`, …). An icon whose sheet is missing renders as nothing —
|
|
87
|
-
no error, no fallback.
|
|
145
|
+
`items` is a prop — the shell does not build or filter the menu. `label` is
|
|
146
|
+
already translated (`ReactNode`), not a key. `footer: true` pins an entry;
|
|
147
|
+
do not rely on "last item". `avatar` and `headerActions` are props: the shell
|
|
148
|
+
holds no session.
|
|
88
149
|
|
|
89
|
-
|
|
150
|
+
```tsx
|
|
151
|
+
import { ActivityLayout, ActivityContent } from '@nxgt/material/components';
|
|
152
|
+
|
|
153
|
+
const items = [
|
|
154
|
+
{ label: 'Home', url: '/', icon: 'house' },
|
|
155
|
+
{ label: 'Settings', url: '/settings', icon: 'gear', footer: true },
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
<ActivityLayout items={items} brand={<Logo />} avatar={<Avatar />}>
|
|
159
|
+
<Outlet />
|
|
160
|
+
</ActivityLayout>
|
|
161
|
+
```
|
|
90
162
|
|
|
91
|
-
|
|
163
|
+
The active entry is the **longest** match across the whole menu, segment-aware
|
|
164
|
+
(`/admin` does not cover `/administrators`). `AppBreadcrumb` reads
|
|
165
|
+
`handle.breadcrumb` off `useMatches()` and needs a data router
|
|
166
|
+
(`createMemoryRouter` + `RouterProvider`, not `MemoryRouter`).
|
|
167
|
+
|
|
168
|
+
## SSR
|
|
169
|
+
|
|
170
|
+
An SSR app that bundles this package (`ssr.noExternal: ['@nxgt/material']`)
|
|
171
|
+
evaluates every module the barrels reach at **boot**, whether or not a page
|
|
172
|
+
renders the component.
|
|
173
|
+
|
|
174
|
+
- **Nothing browser-only at module scope.** `pdfjs-dist` runs `new DOMMatrix()`
|
|
175
|
+
at import. Reach it through `import()` and wrap the component with
|
|
176
|
+
`clientOnly` from `@nxgt/material/lib`. A `typeof window` guard around a
|
|
177
|
+
statement does nothing when the *import* is the side effect.
|
|
178
|
+
- **Never `<svg><title>`.** React 19 hoists `<title>` as document metadata:
|
|
179
|
+
server emits `<title></title>`, client renders the text, hydration discards
|
|
180
|
+
the whole tree. Use `role="img"` + `aria-label` (`Icon` already does).
|
|
181
|
+
|
|
182
|
+
## Traps
|
|
183
|
+
|
|
184
|
+
| Symptom | Cause | Fix |
|
|
185
|
+
| --- | --- | --- |
|
|
186
|
+
| Components render unstyled, no console error | Tailwind did not scan the package | `@source "../node_modules/@nxgt/material/"` |
|
|
187
|
+
| `<Icon>` is blank | sprite sheet not served | `public/assets/icons/sprites/<style>.svg` |
|
|
188
|
+
| Package strings vanish after adding i18n | app declared `material` | never a top-level `material` key in app resources |
|
|
189
|
+
| Hydration mismatch on every page | `<svg><title>` | `role="img"` + `aria-label` |
|
|
190
|
+
| `ReferenceError: DOMMatrix` at server boot | static `react-pdf` import | `import()` + `clientOnly` |
|
|
191
|
+
|
|
192
|
+
## Development
|
|
92
193
|
|
|
93
194
|
```sh
|
|
94
195
|
bun install
|
|
95
|
-
bun run build #
|
|
196
|
+
bun run build # what consumers read
|
|
96
197
|
bun run storybook
|
|
97
198
|
bun run test
|
|
98
199
|
```
|
|
99
|
-
|
|
100
|
-
`bun run build` is what consumers read. There is **no `postinstall` build**: the
|
|
101
|
-
package publishes `dist/` and not `lib/`, so a build triggered on a consumer's
|
|
102
|
-
machine would have no sources to compile and would fail the install outright
|
|
103
|
-
under npm. Build here, then publish.
|
|
104
|
-
|
|
105
|
-
New reusable components arrive here rather than in an app — see the
|
|
106
|
-
"Reusable UI components belong in `@nxgt/material`" section of the consuming
|
|
107
|
-
repositories' `AGENTS.md`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/material",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -62,8 +62,11 @@
|
|
|
62
62
|
"postbuild": "cp lib/styles.css dist/",
|
|
63
63
|
"storybook": "storybook dev -p 6006",
|
|
64
64
|
"build-storybook": "storybook build",
|
|
65
|
-
"publish": "bun publish --access=public",
|
|
66
65
|
"test": "vitest run",
|
|
66
|
+
"changeset": "changeset",
|
|
67
|
+
"changeset:version": "changeset version",
|
|
68
|
+
"changeset:publish": "bun run build && bun run scripts/publish.ts",
|
|
69
|
+
"changeset:status": "changeset status --since=origin/develop",
|
|
67
70
|
"shadcn:add": "bunx --bun shadcn@latest add",
|
|
68
71
|
"tsc": "tsc -b",
|
|
69
72
|
"check": "biome check --write",
|
|
@@ -151,6 +154,8 @@
|
|
|
151
154
|
},
|
|
152
155
|
"devDependencies": {
|
|
153
156
|
"@biomejs/biome": "2.5.5",
|
|
157
|
+
"@changesets/changelog-github": "^1.0.1",
|
|
158
|
+
"@changesets/cli": "^3.0.1",
|
|
154
159
|
"@chromatic-com/storybook": "^5.3.1",
|
|
155
160
|
"@storybook/addon-a11y": "10.6.0",
|
|
156
161
|
"@storybook/addon-docs": "10.6.0",
|