@nxgt/mail-ui 0.1.0

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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +311 -0
  3. package/components/alert.vue +75 -0
  4. package/components/avatar-fallback.vue +21 -0
  5. package/components/avatar-group.vue +44 -0
  6. package/components/avatar-image.vue +20 -0
  7. package/components/avatar.vue +29 -0
  8. package/components/badge.vue +47 -0
  9. package/components/banner.vue +57 -0
  10. package/components/breakdown-card.vue +27 -0
  11. package/components/card-content.vue +16 -0
  12. package/components/card-description.vue +16 -0
  13. package/components/card-footer.vue +16 -0
  14. package/components/card-header.vue +33 -0
  15. package/components/card-title.vue +20 -0
  16. package/components/card.vue +30 -0
  17. package/components/chip.vue +41 -0
  18. package/components/code.vue +26 -0
  19. package/components/compare-card.vue +63 -0
  20. package/components/description.vue +24 -0
  21. package/components/entity-header.vue +48 -0
  22. package/components/goal-card.vue +33 -0
  23. package/components/hero.vue +36 -0
  24. package/components/layout.vue +85 -0
  25. package/components/link.vue +19 -0
  26. package/components/list-tile.vue +80 -0
  27. package/components/nx-button.vue +91 -0
  28. package/components/progress.vue +66 -0
  29. package/components/ratio-card.vue +36 -0
  30. package/components/see-also.vue +53 -0
  31. package/components/separator.vue +27 -0
  32. package/components/stat-card.vue +43 -0
  33. package/components/status-indicator.vue +35 -0
  34. package/components/steps-item.vue +48 -0
  35. package/components/steps.vue +31 -0
  36. package/components/summary-data.vue +45 -0
  37. package/components/table-body.vue +11 -0
  38. package/components/table-caption.vue +16 -0
  39. package/components/table-cell.vue +28 -0
  40. package/components/table-empty.vue +23 -0
  41. package/components/table-footer.vue +11 -0
  42. package/components/table-head.vue +25 -0
  43. package/components/table-header.vue +11 -0
  44. package/components/table-row.vue +15 -0
  45. package/components/table.vue +25 -0
  46. package/components/timeline.vue +70 -0
  47. package/components/typography.vue +64 -0
  48. package/components/ui.ts +166 -0
  49. package/dist/catalogues.d.ts +56 -0
  50. package/dist/catalogues.d.ts.map +1 -0
  51. package/dist/index.d.ts +27 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +214 -0
  54. package/dist/index.js.map +13 -0
  55. package/dist/packaged.d.ts +27 -0
  56. package/dist/packaged.d.ts.map +1 -0
  57. package/dist/plugin.d.ts +55 -0
  58. package/dist/plugin.d.ts.map +1 -0
  59. package/dist/theme.d.ts +11 -0
  60. package/dist/theme.d.ts.map +1 -0
  61. package/dist/vue.d.ts +9 -0
  62. package/dist/vue.d.ts.map +1 -0
  63. package/docs/README.md +15 -0
  64. package/docs/guide/components.md +984 -0
  65. package/docs/guide/messages.md +156 -0
  66. package/docs/guide/plugin.md +365 -0
  67. package/docs/guide/theme.md +153 -0
  68. package/docs/roadmap.md +97 -0
  69. package/docs/troubleshooting.md +519 -0
  70. package/package.json +69 -0
  71. package/theme.css +292 -0
@@ -0,0 +1,97 @@
1
+ # Roadmap
2
+
3
+ Where `@nxgt/mail-ui` is heading. A direction, not a commitment: there are
4
+ no dates here, and the version something shipped in is the only number.
5
+
6
+ ## Now
7
+
8
+ Nothing between releases.
9
+
10
+ ## Next
11
+
12
+ Nothing yet.
13
+
14
+ ## Later
15
+
16
+ - **`NxSpacer`** — a spacer in the package's own style. Until then, Maizzle's
17
+ `<Spacer>` does the job.
18
+
19
+ A request is welcome as an
20
+ [issue](https://github.com/softistx/nxgt-mail/issues).
21
+
22
+ ## Not planned
23
+
24
+ - **Dark mode** — an e-mail is light only: `theme.css` holds material-vue's
25
+ light tokens and no dark ones, so every client starts from the same
26
+ colours.
27
+ - **Icon fonts** — a mail client loads no icon font. An icon is an image by
28
+ absolute URL or a character, passed through the `icon` slot of the
29
+ components that have one.
30
+ - **Transparent colours, blurs and gradients** — a mail client drops an
31
+ alpha, so a tint is flattened to a plain colour; `NxHero` comes without
32
+ its blur or gradient for the same reason.
33
+ - **A dependency on `@nxgt/material-vue`** — the components follow its names,
34
+ props and tokens, but are written for e-mail, with tables and inlined
35
+ styles; nothing of material-vue is installed or imported.
36
+ - **Interactive components** — menus, dialogs, tabs, form fields: an e-mail
37
+ runs no script, so only the components that make sense on a page that is
38
+ read, not used, are mirrored.
39
+ - **A relative logo or brand link** — refused by `ui()`: a mail client loads
40
+ nothing relative, so `brand.logo.src` and `brand.url` are absolute
41
+ `http(s)` URLs.
42
+
43
+ ## Shipped
44
+
45
+ The last ten, newest first, each with the version it came in. Everything
46
+ before is in the [CHANGELOG](../CHANGELOG.md).
47
+
48
+ - **UI components as a plugin, v0.1.0** — `ui({ brand, theme })`, listed in
49
+ `defineMailConfig`'s `plugins`: the `Nx*` components available in every
50
+ template, the brand (name, link, logo by absolute URL) in the layout's
51
+ header and footer, and a wrong option refused where the config is written.
52
+ A project's `components/nx-button.vue` replaces ours by name.
53
+ - **The first set of components, in the style of `@nxgt/material-vue`, v0.1.0** —
54
+ `NxLayout`, `NxTypography`, `NxButton`, `NxLink`, `NxSeparator`, `NxCard`
55
+ and its parts, `NxBadge`, `NxAlert`, `NxBanner`, `NxStatusIndicator`,
56
+ `NxSummaryData`, and `NxCode` for a one-time code: material-vue's names with
57
+ the `Nx` prefix and its `variant`, `color` and `size` props, rendered with
58
+ tables and inlined styles, and checked in the package's tests against the
59
+ support data of Gmail, Outlook and Apple Mail.
60
+ - **The theme, `theme.css`, v0.1.0** — material-vue's light tokens, overridden by name
61
+ with `ui({ theme: { 'color-primary': '#0f766e' } })`; its tints
62
+ (`bg-primary-15`) are flattened to plain colours, and follow a colour you
63
+ override.
64
+ - **Shared messages, v0.1.0** — `uiCatalogues`: `common.greeting`,
65
+ `common.footer.why`, `common.footer.ignore`, `common.avatarGroup.more`,
66
+ `common.timeline.empty`, `common.metrics.ofTarget`,
67
+ `common.metrics.thisPeriod`, `common.metrics.lastPeriod` and
68
+ `common.seeAlso` in `en` and `fr`, given to
69
+ `@nxgt/mail-i18n` as `i18n({ catalogues: [uiCatalogues] })`, and overridden
70
+ key by key by your own `locales/<locale>.json`.
71
+ - **Components and templates installed from npm, v0.1.0** — `ui()` resolves the
72
+ tags of a `.vue` file under `node_modules` itself, since Maizzle does not:
73
+ ours, and a package's templates such as `@nxgt/mail-presets`', render the
74
+ same installed as in the workspace, with your `components/` still replacing
75
+ ours by name.
76
+ - **`brand` typed in the editor, v0.1.0** — `ui()` writes
77
+ `.maizzle/nxgt-mail-ui.d.ts`, which a Maizzle project's `tsconfig.json`
78
+ includes, so templates see `brand` without importing anything.
79
+ - **The second set, first part, v0.1.0** — `NxTable` and its parts (a footer on the
80
+ muted background, a caption, an empty row), `NxDescription`, `NxListTile`,
81
+ `NxChip` (static) and `NxAvatar`/`NxAvatarGroup`, with material-vue's props,
82
+ checked against the same support data.
83
+ - **The second set, second part, v0.1.0** — `NxProgress`, `NxSteps`/`NxStepsItem`
84
+ and `NxTimeline`: a bar, numbered steps and toned events joined by a line
85
+ that runs as far as their text in every client, the time of an event
86
+ written as you give it.
87
+ - **The second set, third part, v0.1.0** — `NxHero`, `NxEntityHeader`, the metric
88
+ cards `NxStatCard`, `NxGoalCard`, `NxRatioCard`, `NxCompareCard` and
89
+ `NxBreakdownCard`, and `NxSeeAlso`: a summary e-mail's header, figures with
90
+ a toned delta and its arrow, bars and links, checked against the same
91
+ support data. With it, every material-vue component that fits an e-mail —
92
+ one that is read, not used — has its `Nx` counterpart.
93
+ - **A starter project, with v0.1.0** — the official Maizzle starter with
94
+ `defineMailConfig`, the i18n and the UI plugins wired in as the READMEs
95
+ say, built, rendered in `en` and `fr` and served in CI, so the snippets are
96
+ known to work: [`examples/starter`](https://github.com/softistx/nxgt-mail/tree/develop/examples/starter).
97
+ In the repository; its README says how to start your own from npm.
@@ -0,0 +1,519 @@
1
+ # Troubleshooting `@nxgt/mail-ui`
2
+
3
+ Each entry is headed by the message you see. The last section holds the traps
4
+ that fail no build — a build that succeeds and is wrong. Search this page for
5
+ the words of your message.
6
+
7
+ How the messages are shaped:
8
+
9
+ - **A wiring mistake is a `TypeError` starting `ui:`**, thrown by the `ui()`
10
+ call in `maizzle.config.ts` when the config loads, so `maizzle build` and
11
+ `maizzle serve` stop before any template is built. Fix the call. One
12
+ failure at that moment is a plain `Error` instead, since no option causes
13
+ it: `ui: @maizzle/framework is not installed beside @nxgt/mail-ui`.
14
+ - **A build failure is a plain `Error`**, thrown while `maizzle build` renders
15
+ a template, and printed after Vue's own
16
+ `[Vue warn]: Unhandled error during execution of setup function`. It names
17
+ the component and what to fix. It has no `code`: it is a mistake in the
18
+ config, not a condition to catch.
19
+ - **A message never holds a value you passed**: it names the option or the
20
+ token, never the URL or the colour.
21
+
22
+ The samples below use the locales `en` and `fr`, the template
23
+ `emails/welcome.vue`, and the brand `Acme`.
24
+
25
+ ## Index
26
+
27
+ **Wiring** — when `maizzle.config.ts` loads
28
+ - [`ui: options must be an object, as { brand: { name: 'Acme' } }`](#ui-options-must-be-an-object-as--brand--name-acme--)
29
+ - [`ui: brand must be an object, as { name: 'Acme', url: 'https://acme.example' }`](#ui-brand-must-be-an-object-as--name-acme-url-httpsacmeexample-)
30
+ - [`ui: brand.name must be the name the e-mails show`](#ui-brandname-must-be-the-name-the-e-mails-show)
31
+ - [`ui: brand.url must be an absolute http(s) URL`](#ui-brandurl-must-be-an-absolute-https-url)
32
+ - [`ui: brand.logo.src must be an absolute http(s) URL — a mail client loads nothing relative`](#ui-brandlogosrc-must-be-an-absolute-https-url--a-mail-client-loads-nothing-relative)
33
+ - [`ui: brand.logo.width must be a width in pixels`](#ui-brandlogowidth-must-be-a-width-in-pixels)
34
+ - [`ui: brand.logo.alt must be a string`](#ui-brandlogoalt-must-be-a-string)
35
+ - [`ui: theme must be an object of tokens, as { 'color-primary': '#0f766e' }`](#ui-theme-must-be-an-object-of-tokens-as--color-primary-0f766e-)
36
+ - [`ui: theme.--color-primary is not a token of the theme — name one of theme.css without its --, as color-primary`](#ui-theme--color-primary-is-not-a-token-of-the-theme--name-one-of-themecss-without-its----as-color-primary)
37
+ - [`ui: theme.color-primary must be a CSS value, as #0f766e or 8px`](#ui-themecolor-primary-must-be-a-css-value-as-0f766e-or-8px)
38
+ - [`ui: @maizzle/framework is not installed beside @nxgt/mail-ui`](#ui-maizzleframework-is-not-installed-beside-nxgtmail-ui)
39
+
40
+ **Build** — while `maizzle build` renders
41
+ - [`[Vue warn]: Failed to resolve component: NxLayout`](#vue-warn-failed-to-resolve-component-nxlayout)
42
+ - [`NxLayout: ui() is not in the plugins of defineMailConfig`](#nxlayout-ui-is-not-in-the-plugins-of-definemailconfig)
43
+ - [`i18n: en: welcome calls t('common.footer.why'), which is not a key of the catalogues`](#i18n-en-welcome-calls-tcommonfooterwhy-which-is-not-a-key-of-the-catalogues)
44
+ - [`NxProgress: modelValue must be a number known when the e-mail is built — a placeholder is filled only when it is sent`](#nxprogress-modelvalue-must-be-a-number-known-when-the-e-mail-is-built--a-placeholder-is-filled-only-when-it-is-sent)
45
+
46
+ **Traps: a build that succeeds and is wrong**
47
+ - [A tint written with an alpha (`bg-primary/15`) is missing in Outlook](#a-tint-written-with-an-alpha-bg-primary15-is-missing-in-outlook)
48
+ - [A side border (`border-b`) is gone, and the style ends with `border: 0`](#a-side-border-border-b-is-gone-and-the-style-ends-with-border-0)
49
+ - [A project's own component does not replace the package's](#a-projects-own-component-does-not-replace-the-packages)
50
+ - [An element placed directly in `NxCard` breaks the card](#an-element-placed-directly-in-nxcard-breaks-the-card)
51
+ - [`class="mb-0"` leaves the space under a boxed component (`NxAlert`, `NxCard`, `NxHero`, the metric cards…)](#classmb-0-leaves-the-space-under-a-boxed-component-nxalert-nxcard-nxhero-the-metric-cards)
52
+ - [The editor says `Property 'brand' does not exist` in a template](#the-editor-says-property-brand-does-not-exist-in-a-template)
53
+ - [Biome reports `parse` errors in a template as soon as you edit it](#biome-reports-parse-errors-in-a-template-as-soon-as-you-edit-it)
54
+ - [A bug in `@nxgt/mail-ui` itself](#a-bug-in-nxgtmail-ui-itself)
55
+
56
+ ---
57
+
58
+ ## Wiring
59
+
60
+ ### `ui: options must be an object, as { brand: { name: 'Acme' } }`
61
+
62
+ **When:** loading `maizzle.config.ts`, when `ui` is called with nothing, or
63
+ with the brand's name alone: `ui()`, `ui('Acme')`.
64
+ **Why:** `ui` takes one object of options; `brand` is the one it requires,
65
+ because the layout's header and footer show it.
66
+ **Fix:**
67
+
68
+ ```ts
69
+ // maizzle.config.ts
70
+ import { defineMailConfig } from '@nxgt/mail-config';
71
+ import { ui } from '@nxgt/mail-ui';
72
+
73
+ export default defineMailConfig({
74
+ plugins: [ui({ brand: { name: 'Acme' } })], // not ui('Acme')
75
+ });
76
+ ```
77
+
78
+ ### `ui: brand must be an object, as { name: 'Acme', url: 'https://acme.example' }`
79
+
80
+ **When:** loading `maizzle.config.ts`, when `brand` is missing, or is the
81
+ name alone: `ui({})`, `ui({ brand: 'Acme' })`.
82
+ **Why:** the brand is a name, and optionally a link and a logo; it is an
83
+ object so that the last two can be added.
84
+ **Fix:**
85
+
86
+ ```ts
87
+ ui({ brand: { name: 'Acme', url: 'https://acme.example' } });
88
+ ```
89
+
90
+ ### `ui: brand.name must be the name the e-mails show`
91
+
92
+ **When:** loading `maizzle.config.ts`, when `brand.name` is missing, not a
93
+ string, or blank — often an environment variable that is not set:
94
+ `{ name: process.env.BRAND_NAME }`.
95
+ **Why:** the header shows the name when there is no logo, and the footer
96
+ always does; a blank name would leave both empty.
97
+ **Fix:**
98
+
99
+ ```ts
100
+ ui({ brand: { name: 'Acme' } });
101
+ ```
102
+
103
+ ### `ui: brand.url must be an absolute http(s) URL`
104
+
105
+ **When:** loading `maizzle.config.ts`, when `brand.url` is set to a relative
106
+ path (`/home`), a bare domain (`acme.example`), another scheme, or a string
107
+ with a space in it.
108
+ **Why:** the header and the footer link to it, and a mail client has no page
109
+ to resolve a relative link against.
110
+ **Fix:**
111
+
112
+ ```ts
113
+ ui({ brand: { name: 'Acme', url: 'https://acme.example' } });
114
+ ```
115
+
116
+ Leave `url` out for a header and a footer without a link.
117
+
118
+ ### `ui: brand.logo.src must be an absolute http(s) URL — a mail client loads nothing relative`
119
+
120
+ **When:** loading `maizzle.config.ts`, when `brand.logo` is set and its `src`
121
+ is missing, relative (`/logo.png`, `images/logo.png`), or not a string — or
122
+ when `logo` is the URL alone: `logo: 'https://…'`.
123
+ **Why:** the image is loaded by the mail client, from wherever the e-mail is
124
+ read; only an absolute URL points somewhere from there.
125
+ **Fix:**
126
+
127
+ ```ts
128
+ ui({
129
+ brand: {
130
+ name: 'Acme',
131
+ logo: { src: 'https://acme.example/logo.png', width: 96 }, // not logo: '…'
132
+ },
133
+ });
134
+ ```
135
+
136
+ Host the image where the recipients can reach it; `public/` in the project is
137
+ served by `maizzle serve`, not by the mail client.
138
+
139
+ ### `ui: brand.logo.width must be a width in pixels`
140
+
141
+ **When:** loading `maizzle.config.ts`, when `brand.logo.width` is a string
142
+ (`'96px'`, `'100%'`), zero, negative, or not a whole number.
143
+ **Why:** it is written as the image's `width` attribute, which a mail client
144
+ reads as a whole number of pixels.
145
+ **Fix:**
146
+
147
+ ```ts
148
+ logo: { src: 'https://acme.example/logo.png', width: 96 }
149
+ ```
150
+
151
+ Leave `width` out for the default, `120`.
152
+
153
+ ### `ui: brand.logo.alt must be a string`
154
+
155
+ **When:** loading `maizzle.config.ts`, when `brand.logo.alt` is set to
156
+ something else than a string, such as `null` or `false`.
157
+ **Why:** it is written as the image's `alt`, the text shown while images are
158
+ blocked.
159
+ **Fix:**
160
+
161
+ ```ts
162
+ logo: { src: 'https://acme.example/logo.png', alt: 'Acme' }
163
+ ```
164
+
165
+ Leave `alt` out to use the brand's name.
166
+
167
+ ### `ui: theme must be an object of tokens, as { 'color-primary': '#0f766e' }`
168
+
169
+ **When:** loading `maizzle.config.ts`, when `theme` is a list, a string (the
170
+ path to a CSS file), or `null`.
171
+ **Why:** `theme` overrides tokens of the package's theme one by one, by
172
+ name; it is not a replacement stylesheet.
173
+ **Fix:**
174
+
175
+ ```ts
176
+ ui({
177
+ brand: { name: 'Acme' },
178
+ theme: { 'color-primary': '#0f766e', 'radius-lg': '4px' },
179
+ });
180
+ ```
181
+
182
+ Leave `theme` out for the package's theme as it is.
183
+
184
+ ### `ui: theme.--color-primary is not a token of the theme — name one of theme.css without its --, as color-primary`
185
+
186
+ The message names the token you passed.
187
+
188
+ **When:** loading `maizzle.config.ts`, for a token written as CSS writes it
189
+ (`'--color-primary'`), in camelCase (`colorPrimary`), misspelt
190
+ (`color-primery`), or not declared by the theme (`color-brand`).
191
+ **Why:** `theme` only overrides the tokens `@nxgt/mail-ui/theme.css`
192
+ declares, named without their `--`; a token it does not declare would style
193
+ nothing, so it is refused rather than ignored.
194
+ **Fix:**
195
+
196
+ ```ts
197
+ theme: { 'color-primary': '#0f766e' } // not '--color-primary'
198
+ ```
199
+
200
+ The tokens are the `--…` lines of `node_modules/@nxgt/mail-ui/theme.css`:
201
+ colours (`color-primary`, `color-error`, …) and radii (`radius-sm` to
202
+ `radius-xl`). A colour's tints (`color-primary-15`, …) follow it when you
203
+ override the colour; a tint can also be overridden alone.
204
+
205
+ ### `ui: theme.color-primary must be a CSS value, as #0f766e or 8px`
206
+
207
+ The message names the token you passed.
208
+
209
+ **When:** loading `maizzle.config.ts`, when a token's value is not a string
210
+ (`'radius-lg': 4`), is empty, or holds a `;`, a brace, an angle bracket, a
211
+ quote, a backslash, a CSS comment or a line break — `'#0f766e; color: red'`,
212
+ `"'#0f766e' /* brand */"`.
213
+ **Why:** each value is written into one CSS declaration; anything that could
214
+ end it or open another is refused.
215
+ **Fix:**
216
+
217
+ ```ts
218
+ theme: { 'color-primary': '#0f766e', 'radius-lg': '4px' } // not 4
219
+ ```
220
+
221
+ ### `ui: @maizzle/framework is not installed beside @nxgt/mail-ui`
222
+
223
+ This one is a plain `Error`, not a `TypeError`: the call is right, the
224
+ install is not.
225
+
226
+ **When:** loading `maizzle.config.ts`, when `ui()` finds no
227
+ `@maizzle/framework` in any `node_modules` folder above the one it is
228
+ installed in. It happens when `@maizzle/framework` is not a dependency of the
229
+ project, or when `@nxgt/mail-ui` is linked from a folder outside the project
230
+ (`bun link`, a `file:` or `link:` dependency).
231
+ **Why:** `ui()` resolves the tags of templates and components installed from
232
+ npm itself, with Maizzle's built-in components (`<Container>`, `<Spacer>`,
233
+ …) as the last choice. It finds them where Node would find the package, up
234
+ from its own folder. `@maizzle/framework` is a required peer.
235
+ **Fix:** install Maizzle in the project, beside `@nxgt/mail-ui`, and install
236
+ the package from the registry rather than linking it:
237
+
238
+ ```sh
239
+ bun add @nxgt/mail-ui @maizzle/framework
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Build
245
+
246
+ These fail while `maizzle build` renders a template: the build stops, and the
247
+ line to read starts `Error:`, after Vue's warning and a stack trace.
248
+
249
+ ### `[Vue warn]: Failed to resolve component: NxLayout`
250
+
251
+ One such warning for each tag that did not resolve: `NxTypography`,
252
+ `NxButton`, …
253
+
254
+ **When:** `maizzle build`, when `ui()` is not in the plugins. What follows
255
+ depends on the template: `TypeError: Cannot read properties of undefined
256
+ (reading 'name')` when it reads `brand.name`; with `@nxgt/mail-i18n`,
257
+ `i18n: en/welcome.html is empty — a tag of its template resolved to no
258
+ component; list the plugin that brings it, as ui()` after the build; without
259
+ it, nothing. The build then succeeds, and the built e-mail holds its doctype
260
+ and nothing else.
261
+ **Why:** Vue renders a component it cannot resolve as nothing. Maizzle
262
+ resolves the tags of the project's own templates from its `components`
263
+ folders, but skips every file under `node_modules`: the package's components,
264
+ and templates installed from npm such as `@nxgt/mail-presets`'s. `ui()`
265
+ resolves the tags of those files: the project's `components/` first, then
266
+ the `Nx*` components, then Maizzle's built-ins.
267
+ **Fix:** list `ui()`, rather than registering `COMPONENTS_DIR` in
268
+ `components.source`:
269
+
270
+ ```ts
271
+ // maizzle.config.ts
272
+ import { defineMailConfig } from '@nxgt/mail-config';
273
+ import { ui } from '@nxgt/mail-ui';
274
+
275
+ export default defineMailConfig({
276
+ plugins: [ui({ brand: { name: 'Acme' } })],
277
+ });
278
+ ```
279
+
280
+ A template installed from another package, which uses a component that is
281
+ neither the project's, nor an `Nx*` component, nor one of Maizzle's, is left
282
+ unresolved: that package's plugin must resolve it.
283
+
284
+ ### `NxLayout: ui() is not in the plugins of defineMailConfig`
285
+
286
+ **When:** `maizzle build`, on the first template that uses `<NxLayout>`, when
287
+ the project registers the package's components itself — `COMPONENTS_DIR` in
288
+ `components.source` — without calling `ui()`.
289
+ **Why:** the layout reads the brand and the theme that `ui()` provides.
290
+ Registering the folder makes `<NxLayout>` resolve, but provides neither.
291
+ **Fix:** register the components through `ui()`, which does both:
292
+
293
+ ```ts
294
+ // maizzle.config.ts
295
+ import { defineMailConfig } from '@nxgt/mail-config';
296
+ import { ui } from '@nxgt/mail-ui';
297
+
298
+ export default defineMailConfig({
299
+ plugins: [ui({ brand: { name: 'Acme' } })],
300
+ // no components.source pointing at COMPONENTS_DIR
301
+ });
302
+ ```
303
+
304
+ ### `i18n: en: welcome calls t('common.footer.why'), which is not a key of the catalogues`
305
+
306
+ The message names your locale and template; the key starts `common.`.
307
+
308
+ **When:** `maizzle build`, on the first template that uses `<NxLayout>` (its
309
+ footer calls `t('common.footer.why')`), or an `<NxAvatarGroup>` hiding avatars
310
+ past its `max` (its `+N` calls `t('common.avatarGroup.more')`), an
311
+ `<NxTimeline>` with no events and no `empty`
312
+ (`t('common.timeline.empty')`), an `<NxGoalCard>`
313
+ (`t('common.metrics.ofTarget')`), an `<NxCompareCard>` whose boxes have no
314
+ `label` (`t('common.metrics.thisPeriod')`, `t('common.metrics.lastPeriod')`),
315
+ an `<NxSeeAlso>` with links and no `label` (`t('common.seeAlso')`), or that calls
316
+ `t('common.greeting')` or another `common.` key, when `@nxgt/mail-i18n` is in the plugins without
317
+ the package's messages.
318
+ **Why:** the `common.*` messages ship in `uiCatalogues`, not in the
319
+ project's `locales/`. The i18n plugin only knows them when they are passed
320
+ to it.
321
+ **Fix:**
322
+
323
+ ```ts
324
+ // maizzle.config.ts
325
+ import { defineMailConfig } from '@nxgt/mail-config';
326
+ import { i18n } from '@nxgt/mail-i18n';
327
+ import { ui, uiCatalogues } from '@nxgt/mail-ui';
328
+
329
+ export default defineMailConfig({
330
+ plugins: [
331
+ ui({ brand: { name: 'Acme' } }),
332
+ i18n({ locales: ['en', 'fr'], catalogues: [uiCatalogues] }),
333
+ ],
334
+ });
335
+ ```
336
+
337
+ `uiCatalogues` holds `en` and `fr`. A project in another locale writes the
338
+ `common` keys in its own `locales/<locale>.json`; a key there overrides the
339
+ package's, key by key.
340
+
341
+ ---
342
+
343
+ ### `NxProgress: modelValue must be a number known when the e-mail is built — a placeholder is filled only when it is sent`
344
+
345
+ The prop named is `modelValue`, `max` or `height` (which must also be above 0).
346
+
347
+ **When:** `maizzle build`, on a template whose `<NxProgress>` is given a
348
+ placeholder or any value that is not a number:
349
+ `<NxProgress :model-value="placeholder('share')" />`,
350
+ `<NxProgress model-value="40" />` (a string, without the `:`). The cards that
351
+ draw an `NxProgress` fail the same way: `<NxGoalCard>`'s `value`
352
+ (`modelValue`) and `target` (`max`), `<NxRatioCard>`'s `percent` and each
353
+ `percent` of `<NxBreakdownCard>`'s `items` (`modelValue`) —
354
+ `<NxGoalCard label="Seats" :value="placeholder('used')" :target="10" />`.
355
+ **Why:** the bar's width is computed when the e-mail is built; a placeholder
356
+ is only filled when it is sent. Rather than draw an empty bar, the build stops.
357
+ **Fix:** pass a number with `v-bind`. For a share that differs per recipient,
358
+ write it as text, which a placeholder fills — or use an `<NxStatCard>`, which
359
+ draws no bar:
360
+
361
+ ```vue
362
+ <template>
363
+ <NxProgress :model-value="2" :max="3" />
364
+ <NxTypography>{{ t('onboarding.progress', { done: placeholder('done') }) }}</NxTypography>
365
+ </template>
366
+ ```
367
+
368
+ ## Traps: a build that succeeds and is wrong
369
+
370
+ ### A tint written with an alpha (`bg-primary/15`) is missing in Outlook
371
+
372
+ **When:** `maizzle build` succeeds, and an element written with a colour and
373
+ an opacity — `bg-primary/15`, `border-error/50` — has
374
+ `background-color: rgba(72, 80, 150, .15)` in its style. Outlook on Windows
375
+ shows no background there.
376
+ **Why:** Outlook drops a colour with an alpha. And Maizzle rewrites the class
377
+ `bg-primary/15` to `bg-primary-15` in the HTML, so in the same template the
378
+ `rgba()` also lands on elements written with the package's `bg-primary-15`.
379
+ **Fix:** write the theme's tint, which is the same colour mixed over the
380
+ background into a plain hex value:
381
+
382
+ ```vue
383
+ <td class="bg-primary-15">…</td> <!-- not bg-primary/15 -->
384
+ ```
385
+
386
+ Each colour of the theme (`primary`, `secondary`, `info`, `success`,
387
+ `warning`, `error`, `foreground`) has the tints `-5`, `-10`, `-15`, `-20`,
388
+ `-25`, `-40` and `-50`, for `bg-`, `text-` and `border-`.
389
+
390
+ ### A side border (`border-b`) is gone, and the style ends with `border: 0`
391
+
392
+ **When:** `maizzle build` succeeds, and an element written as
393
+ `border-0 border-b border-solid border-primary-40` has no bottom border; its
394
+ style reads `border-bottom-style: solid; border-bottom-width: 1px; border: 0 solid #b6b9d5`.
395
+ **Why:** Maizzle merges the width, style and colour into one `border`
396
+ shorthand and writes it after the per-side declarations, so `border: 0` wins
397
+ over `border-bottom-width`. Without `border-0`, `border-solid` would draw the
398
+ other three sides at the default width instead.
399
+ **Fix:** leave out `border-0` and `border-solid`, and set the style of that
400
+ side only:
401
+
402
+ ```vue
403
+ <td class="border-b [border-bottom-style:solid] border-primary-40">…</td>
404
+ ```
405
+
406
+ The style comes out as `border-bottom: 1px solid; border-color: …`, and the
407
+ other sides have none. `NxSummaryData` draws its rows this way.
408
+
409
+ ### A project's own component does not replace the package's
410
+
411
+ **When:** `maizzle build` succeeds, and a template still renders the
412
+ package's `<NxBadge>` although the project has its own badge component.
413
+ **Why:** a project replaces a component by name: its file must be named as
414
+ the component, `Nx` prefix included, in the project's `components/` folder
415
+ (under `root`, when the config sets one), in kebab case or in Pascal case.
416
+ `components/badge.vue` is a different component, `<Badge>` — which is what
417
+ a copy of the package's `badge.vue` is, until renamed: ours are named without
418
+ the prefix, because `ui()` adds it.
419
+ **Fix:**
420
+
421
+ ```
422
+ components/nx-badge.vue → replaces <NxBadge> in every template
423
+ components/NxBadge.vue → the same
424
+ ```
425
+
426
+ The package's components carry the `Nx` prefix so that Maizzle's own
427
+ (`<Button>`, …) stay available; the project's override keeps it.
428
+
429
+ ### An element placed directly in `NxCard` breaks the card
430
+
431
+ **When:** `<NxCard>` holds something other than its parts —
432
+ `<NxCard><p>Text</p></NxCard>`. The built HTML has the `<p>` straight inside
433
+ a `<table>`, which clients render out of place or drop.
434
+ **Why:** the card's body is a `<table>` of rows; each part (`NxCardHeader`,
435
+ `NxCardContent`, `NxCardFooter`) is one `<tr>`.
436
+ **Fix:** put the content in a part:
437
+
438
+ ```vue
439
+ <NxCard>
440
+ <NxCardContent><NxTypography>Text</NxTypography></NxCardContent>
441
+ </NxCard>
442
+ ```
443
+
444
+ ### `class="mb-0"` leaves the space under a boxed component (`NxAlert`, `NxCard`, `NxHero`, the metric cards…)
445
+
446
+ **When:** `<NxAlert class="mb-0">` still has 16px below it.
447
+ **Why:** these components put their `class` on the box inside, where it
448
+ styles the box; the space below is on the table around it.
449
+ **Fix:** replace the component with your own, copied from the package's, and
450
+ change its outer `mb-4` (`mb-2` on `NxListTile`) — see
451
+ [Replacing a component](guide/plugin.md#replacing-a-component). On
452
+ `NxTypography`, `NxSummaryData`, `NxTable`, `NxDescription`,
453
+ `NxAvatarGroup`, `NxProgress`, `NxSteps`, `NxTimeline` and `NxSeeAlso`,
454
+ `class="mb-0"` works. `NxHero`, `NxEntityHeader` and the metric cards
455
+ (`NxStatCard` to `NxBreakdownCard`) keep it, as `NxCard` does: their `class`
456
+ goes on the box.
457
+
458
+ ### The editor says `Property 'brand' does not exist` in a template
459
+
460
+ `Property 'brand' does not exist on type 'ComponentPublicInstance<…>'`, in
461
+ the editor or in `vue-tsc`. The build is not affected: `brand` is there when
462
+ Maizzle renders.
463
+
464
+ **When:** editing a template, in an editor with Vue's language tools, or in
465
+ `vue-tsc`.
466
+ **Why:** the type of `brand` reaches the templates through
467
+ `.maizzle/nxgt-mail-ui.d.ts`, which `ui()` writes each time the config loads.
468
+ Either it has not been written yet — a fresh clone, before any
469
+ `maizzle prepare`, `serve` or `build` — or your `tsconfig.json` does not
470
+ include `.maizzle/*.d.ts`. A project that sets Maizzle's `root`, or a Laravel
471
+ project, has its `.maizzle/` elsewhere: include that one.
472
+ **Fix:** keep the starter's include and write the file once:
473
+
474
+ ```json
475
+ { "include": ["**/*.vue", ".maizzle/*.d.ts"] }
476
+ ```
477
+
478
+ ```sh
479
+ bunx maizzle prepare
480
+ ```
481
+
482
+ See [Typed in the editor and in CI](guide/plugin.md#typed-in-the-editor-and-in-ci).
483
+
484
+ ### Biome reports `parse` errors in a template as soon as you edit it
485
+
486
+ `Expected a property, a shorthand property, a getter, a setter, or a method but
487
+ instead found '{ t('welcome.title')'`, `type assertion are a TypeScript
488
+ only feature`, or `This class property name should be in camelCase` on a
489
+ component's tag — in the editor only; `biome check` reports nothing.
490
+
491
+ **When:** typing in a template that has no `<script>`, with Biome 2.5 as the
492
+ editor's linter.
493
+ **Why:** Biome's language server reads only the `<script>` of a `.vue` file
494
+ when it opens it, but re-reads a file without one as JavaScript from the first
495
+ change on. The template is fine; the editor's Biome is not reading it as Vue.
496
+ **Fix:** let Biome parse Vue templates — add this key to your existing
497
+ `biome.json` — then run **Biome: Restart** in the editor:
498
+
499
+ ```json
500
+ { "html": { "experimentalFullSupportEnabled": true } }
501
+ ```
502
+
503
+ Biome then lints the templates too. A rule that cannot see a slot's text,
504
+ such as `useAnchorContent` on `<a><slot /></a>`, is silenced on its element:
505
+
506
+ ```vue
507
+ <!-- biome-ignore lint/a11y/useAnchorContent: the link's text is the slot. -->
508
+ <a :href="href"><slot /></a>
509
+ ```
510
+
511
+ ### A bug in `@nxgt/mail-ui` itself
512
+
513
+ A component that renders differently from what its props ask for, a colour
514
+ left as `oklch()`, `var(--…)` or `color-mix()` in the built HTML, or a theme
515
+ override that does not reach the tints of its colour, is a bug in this
516
+ package. Open an issue on
517
+ [`softistx/nxgt-mail`](https://github.com/softistx/nxgt-mail/issues) with the
518
+ message or the built HTML, the package version, the Maizzle version, and the
519
+ smallest template that reproduces it — never a real address or link.
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@nxgt/mail-ui",
3
+ "version": "0.1.0",
4
+ "description": "E-mail components for a Maizzle project in the style of @nxgt/material-vue: NxLayout, NxButton, NxCard, NxBadge, NxAlert… with its variants, colours and tokens, rendered with tables and inlined styles.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "components",
12
+ "theme.css",
13
+ "docs",
14
+ "README.md",
15
+ "package.json",
16
+ "LICENSE"
17
+ ],
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./theme.css": "./theme.css",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "keywords": [
28
+ "email",
29
+ "maizzle",
30
+ "components",
31
+ "vue",
32
+ "tailwindcss",
33
+ "material"
34
+ ],
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/softistx/nxgt-mail.git",
38
+ "directory": "packages/mail-ui"
39
+ },
40
+ "publishConfig": {
41
+ "registry": "https://registry.npmjs.org",
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "build": "bun run ../../build.ts",
46
+ "test": "bun test src",
47
+ "typecheck": "tsc --noEmit && bun run typecheck:templates",
48
+ "typecheck:templates": "bun run prepare:templates && cd test/fixture && vue-tsc -p tsconfig.json",
49
+ "prepare:templates": "cd test/fixture && maizzle prepare"
50
+ },
51
+ "dependencies": {
52
+ "unplugin-vue-components": "^32.1.0"
53
+ },
54
+ "devDependencies": {
55
+ "@maizzle/framework": "6.1.7",
56
+ "@maizzle/tailwindcss": "1.5.6",
57
+ "@nxgt/mail-config": "0.1.0",
58
+ "@nxgt/mail-i18n": "0.1.0",
59
+ "@types/bun": "^1.4.2",
60
+ "vue": "3.5.43",
61
+ "vue-tsc": "^3.3.11"
62
+ },
63
+ "peerDependencies": {
64
+ "@maizzle/framework": "^6.1.7",
65
+ "@nxgt/mail-config": "^0.1.0",
66
+ "typescript": "^6.0.3",
67
+ "vue": "^3.5.0"
68
+ }
69
+ }