@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.
Files changed (2) hide show
  1. package/README.md +160 -68
  2. package/package.json +7 -2
package/README.md CHANGED
@@ -1,107 +1,199 @@
1
1
  # @nxgt/material
2
2
 
3
- The shared React component library for the nxgt applications primitives,
4
- form fields, layout shells, the `DataTable`, the drag-and-drop kit, i18n
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
- Built with Tailwind CSS v4 and Base UI. Consumers read `dist/`.
6
+ ## Install
8
7
 
9
- ---
8
+ ```bash
9
+ bun add @nxgt/material
10
+ ```
10
11
 
11
- ## Consuming it
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
- Three things have to be in place. Two are CSS, and the third is the one people
14
- forget: **the icon sprites are yours to supply.**
15
+ ```bash
16
+ bun add react react-dom react-hook-form react-redux @reduxjs/toolkit @tanstack/react-table react-use
17
+ ```
15
18
 
16
- ### 1. Install
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
- ```jsonc
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
- ### 2. The three CSS lines
29
+ ## Setup
26
30
 
27
- In the app's root stylesheet — `app/app.css` in every consumer we have — in
28
- this order:
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
- - **`@import "@nxgt/material/styles.css"`** brings in the design tokens: the
39
- `oklch` colour scale, `--radius`, the `dark` custom variant, the font stacks.
40
- It is `lib/styles.css` shipped verbatim rather than compiled, because Tailwind
41
- v4 wants the source `@theme` layer to run through *your* Tailwind build.
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
- There is a second, compiled stylesheet at `@nxgt/material` (the package's
50
- `style` condition, `dist/material.css`). None of our apps import it; they take
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
- ### 3. The icon sprites — the consumer provides them
49
+ ### Icons
54
50
 
55
- `Icon` renders a `<use>` pointing at a **root-relative** URL:
51
+ `Icon` points at a **root-relative** URL on *your* origin:
56
52
 
57
53
  ```tsx
58
- <use xlinkHref={`/assets/icons/sprites/${style}.svg#${name}`} />
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
- That URL is resolved by the browser against **your** origin, not against the
62
- package. So the app must serve the sprite sheets itself, from:
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
- public/assets/icons/sprites/<style>.svg
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
- **This package deliberately does not ship them.** They are Font Awesome Pro
69
- 7.3.1, whose commercial licence is perpetual in *duration*, not in *scope*: it
70
- covers using the icons in your own products, not redistributing them inside a
71
- library. A published `@nxgt/material` tarball that contained them would be
72
- redistribution regardless of whether this repository is private.
133
+ ```tsx
134
+ <I18nProvider defaultLanguage="en">
135
+ <App />
136
+ </I18nProvider>
137
+ ```
73
138
 
74
- `files` therefore carries an explicit `"!dist/assets"`, so a stale local build
75
- cannot leak them back into a tarball by accident.
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
- **Committing the sheets in a consuming application is a different thing, and it
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
- Each consuming app therefore commits its own sprites into
83
- `public/assets/icons/sprites/`, taken from its own Font Awesome Pro kit. The styles in use across the parc are
84
- the 37 sheets covering `regular`, `light`, `thin`, `solid`, `duotone`,
85
- `sharp-*`, `brands` and the expressive families (`chisel`, `etch`, `jelly`,
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
- ## Working on this package
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 # tsc -b && vite build, then copies lib/styles.css to dist/
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.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",