@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.
- package/LICENSE +21 -0
- package/README.md +311 -0
- package/components/alert.vue +75 -0
- package/components/avatar-fallback.vue +21 -0
- package/components/avatar-group.vue +44 -0
- package/components/avatar-image.vue +20 -0
- package/components/avatar.vue +29 -0
- package/components/badge.vue +47 -0
- package/components/banner.vue +57 -0
- package/components/breakdown-card.vue +27 -0
- package/components/card-content.vue +16 -0
- package/components/card-description.vue +16 -0
- package/components/card-footer.vue +16 -0
- package/components/card-header.vue +33 -0
- package/components/card-title.vue +20 -0
- package/components/card.vue +30 -0
- package/components/chip.vue +41 -0
- package/components/code.vue +26 -0
- package/components/compare-card.vue +63 -0
- package/components/description.vue +24 -0
- package/components/entity-header.vue +48 -0
- package/components/goal-card.vue +33 -0
- package/components/hero.vue +36 -0
- package/components/layout.vue +85 -0
- package/components/link.vue +19 -0
- package/components/list-tile.vue +80 -0
- package/components/nx-button.vue +91 -0
- package/components/progress.vue +66 -0
- package/components/ratio-card.vue +36 -0
- package/components/see-also.vue +53 -0
- package/components/separator.vue +27 -0
- package/components/stat-card.vue +43 -0
- package/components/status-indicator.vue +35 -0
- package/components/steps-item.vue +48 -0
- package/components/steps.vue +31 -0
- package/components/summary-data.vue +45 -0
- package/components/table-body.vue +11 -0
- package/components/table-caption.vue +16 -0
- package/components/table-cell.vue +28 -0
- package/components/table-empty.vue +23 -0
- package/components/table-footer.vue +11 -0
- package/components/table-head.vue +25 -0
- package/components/table-header.vue +11 -0
- package/components/table-row.vue +15 -0
- package/components/table.vue +25 -0
- package/components/timeline.vue +70 -0
- package/components/typography.vue +64 -0
- package/components/ui.ts +166 -0
- package/dist/catalogues.d.ts +56 -0
- package/dist/catalogues.d.ts.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +214 -0
- package/dist/index.js.map +13 -0
- package/dist/packaged.d.ts +27 -0
- package/dist/packaged.d.ts.map +1 -0
- package/dist/plugin.d.ts +55 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/theme.d.ts +11 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/vue.d.ts +9 -0
- package/dist/vue.d.ts.map +1 -0
- package/docs/README.md +15 -0
- package/docs/guide/components.md +984 -0
- package/docs/guide/messages.md +156 -0
- package/docs/guide/plugin.md +365 -0
- package/docs/guide/theme.md +153 -0
- package/docs/roadmap.md +97 -0
- package/docs/troubleshooting.md +519 -0
- package/package.json +69 -0
- package/theme.css +292 -0
package/docs/roadmap.md
ADDED
|
@@ -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
|
+
}
|