@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,70 @@
1
+ <script setup lang="ts">
2
+ import { twMerge } from '@maizzle/framework';
3
+ import { computed, getCurrentInstance, useAttrs } from 'vue';
4
+ import { sharedMessage, type TimelineItem, type TimelineTone } from './ui';
5
+
6
+ /**
7
+ * material-vue's Timeline: events one under the other, each a toned marker
8
+ * on a line, its title, a time on the right, and a description.
9
+ *
10
+ * An e-mail is built before it is sent, so a time is a label you write —
11
+ * a placeholder, as `{{ placeholder('signedInAt') }}` — never a relative time
12
+ * computed at build time. There is no loading state: an e-mail does not load.
13
+ */
14
+ defineOptions({ inheritAttrs: false });
15
+
16
+ const props = defineProps<{ items: readonly TimelineItem[]; empty?: string }>();
17
+
18
+ const MARKER: Record<TimelineTone, string> = {
19
+ default: 'border-border bg-muted text-muted-foreground',
20
+ primary: 'border-primary-40 bg-primary-15 text-primary',
21
+ success: 'border-success-40 bg-success-15 text-success',
22
+ info: 'border-info-40 bg-info-15 text-info',
23
+ warning: 'border-warning-40 bg-warning-15 text-warning',
24
+ error: 'border-error-40 bg-error-15 text-error',
25
+ };
26
+
27
+ const globals: Record<string, unknown> =
28
+ getCurrentInstance()?.appContext.config.globalProperties ?? {};
29
+ const emptyText = computed(
30
+ () =>
31
+ props.empty ??
32
+ sharedMessage(globals, 'common.timeline.empty', 'No activity yet'),
33
+ );
34
+ const attrs = useAttrs();
35
+ const classes = computed(() => twMerge('mb-4 w-full', attrs.class as string));
36
+ const emptyClasses = computed(() =>
37
+ twMerge(
38
+ 'mb-4 py-6 text-center text-sm text-muted-foreground',
39
+ attrs.class as string,
40
+ ),
41
+ );
42
+ </script>
43
+
44
+ <template>
45
+ <table v-if="props.items.length > 0" v-bind="{ ...attrs, class: undefined }" :class="classes" role="presentation" cellpadding="0" cellspacing="0">
46
+ <template v-for="(item, position) in props.items" :key="item.id">
47
+ <tr>
48
+ <td colspan="2" class="w-8 align-top">
49
+ <span :class="`block h-8 w-8 rounded-full border border-solid text-center text-[12px] leading-[30px] ${MARKER[item.tone ?? 'default']}`" aria-hidden="true"><span data-maizzle-html-only>&#9679;</span></span>
50
+ </td>
51
+ <td class="pl-3 align-top">
52
+ <table class="w-full" role="presentation" cellpadding="0" cellspacing="0">
53
+ <tr>
54
+ <td class="pt-1 align-top text-sm font-medium leading-6 text-foreground">{{ item.title }}</td>
55
+ <td v-if="item.timestampLabel" class="whitespace-nowrap pl-3 pt-1 text-right align-top text-xs leading-6 text-muted-foreground">{{ item.timestampLabel }}</td>
56
+ </tr>
57
+ </table>
58
+ </td>
59
+ </tr>
60
+ <tr>
61
+ <td :class="['w-4 text-[1px] leading-px', position < props.items.length - 1 && 'border-r [border-right-style:solid] border-border']"><span data-maizzle-html-only>&zwj;</span></td>
62
+ <td class="w-4 text-[1px] leading-px"><span data-maizzle-html-only>&zwj;</span></td>
63
+ <td :class="['pl-3 align-top', position < props.items.length - 1 && 'pb-6']">
64
+ <p v-if="item.description" class="m-0 text-sm text-muted-foreground">{{ item.description }}</p>
65
+ </td>
66
+ </tr>
67
+ </template>
68
+ </table>
69
+ <p v-else v-bind="{ ...attrs, class: undefined }" :class="emptyClasses">{{ emptyText }}</p>
70
+ </template>
@@ -0,0 +1,64 @@
1
+ <script setup lang="ts">
2
+ import { twMerge } from '@maizzle/framework';
3
+ import { computed, useAttrs } from 'vue';
4
+
5
+ /** material-vue's Typography, on the tag an e-mail reads it as. */
6
+ type Variant =
7
+ | 'normal'
8
+ | 'headline-large'
9
+ | 'headline-medium'
10
+ | 'headline-small'
11
+ | 'title-large'
12
+ | 'title-medium'
13
+ | 'title-small'
14
+ | 'body-medium'
15
+ | 'body-small'
16
+ | 'caption';
17
+
18
+ defineOptions({ inheritAttrs: false });
19
+
20
+ const props = withDefaults(
21
+ defineProps<{
22
+ variant?: Variant;
23
+ /** Default `h1` for a headline, `h2`–`h4` for a title, else `p`. */
24
+ as?: string;
25
+ }>(),
26
+ { variant: 'normal' },
27
+ );
28
+
29
+ const VARIANT: Record<Variant, string> = {
30
+ normal: 'text-sm',
31
+ 'headline-large': 'text-4xl font-semibold',
32
+ 'headline-medium': 'text-3xl font-semibold',
33
+ 'headline-small': 'text-2xl font-semibold',
34
+ 'title-large': 'text-xl font-semibold',
35
+ 'title-medium': 'text-lg font-semibold',
36
+ 'title-small': 'text-base font-semibold',
37
+ 'body-medium': 'text-sm',
38
+ 'body-small': 'text-xs',
39
+ caption: 'text-[10px] text-muted-foreground',
40
+ };
41
+
42
+ const TAG: Partial<Record<Variant, string>> = {
43
+ 'headline-large': 'h1',
44
+ 'headline-medium': 'h1',
45
+ 'headline-small': 'h1',
46
+ 'title-large': 'h2',
47
+ 'title-medium': 'h3',
48
+ 'title-small': 'h4',
49
+ };
50
+
51
+ const attrs = useAttrs();
52
+ const tag = computed(() => props.as ?? TAG[props.variant] ?? 'p');
53
+ const classes = computed(() =>
54
+ twMerge(
55
+ 'm-0 mb-4 text-foreground',
56
+ VARIANT[props.variant],
57
+ attrs.class as string,
58
+ ),
59
+ );
60
+ </script>
61
+
62
+ <template>
63
+ <component :is="tag" v-bind="{ ...attrs, class: undefined }" :class="classes"><slot /></component>
64
+ </template>
@@ -0,0 +1,166 @@
1
+ /**
2
+ * What the components share. Shipped as source: Maizzle compiles it with the
3
+ * components that import it.
4
+ */
5
+ import { Fragment, inject, isVNode, type VNode } from 'vue';
6
+
7
+ /** material-vue's colours; `default` is the foreground. */
8
+ export type Color =
9
+ | 'primary'
10
+ | 'secondary'
11
+ | 'info'
12
+ | 'success'
13
+ | 'warning'
14
+ | 'error'
15
+ | 'default';
16
+
17
+ /** material-vue's tones for a status: its colours but `default`. */
18
+ export type Tone = 'info' | 'success' | 'warning' | 'error';
19
+
20
+ export interface Brand {
21
+ readonly name: string;
22
+ readonly url?: string;
23
+ readonly logo?: {
24
+ readonly src: string;
25
+ readonly width?: number;
26
+ readonly alt?: string;
27
+ };
28
+ }
29
+
30
+ export interface UiContext {
31
+ readonly brand: Brand;
32
+ readonly css: string;
33
+ }
34
+
35
+ /** Which part of an `NxTable` a cell is in, as its `NxTableHeader`/`Body`/`Footer` says. */
36
+ export const TABLE_PART = 'nxgt:mail-ui:table-part';
37
+ export type TablePart = 'header' | 'body' | 'footer';
38
+
39
+ export type TimelineTone =
40
+ | 'default'
41
+ | 'primary'
42
+ | 'success'
43
+ | 'info'
44
+ | 'warning'
45
+ | 'error';
46
+
47
+ /** An event of `NxTimeline`: material-vue's, with a written time only. */
48
+ export interface TimelineItem {
49
+ readonly id: string;
50
+ readonly title: string;
51
+ readonly description?: string;
52
+ /** The time, as written: a placeholder, or a date you format. */
53
+ readonly timestampLabel?: string;
54
+ readonly tone?: TimelineTone;
55
+ }
56
+
57
+ /** The pixel size of the `NxAvatar`s inside, as `NxAvatar` or `NxAvatarGroup` says. */
58
+ export const AVATAR_SIZE = 'nxgt:mail-ui:avatar-size';
59
+
60
+ /** The brand and theme `ui()` provides. A component used without it **throws**. */
61
+ export function useUi(component: string): UiContext {
62
+ const context = inject<UiContext | undefined>('nxgt:mail-ui', undefined);
63
+ if (context === undefined) {
64
+ throw new Error(
65
+ `${component}: ui() is not in the plugins of defineMailConfig`,
66
+ );
67
+ }
68
+ return context;
69
+ }
70
+
71
+ /** material-vue's Button variants, which its Chip takes too. */
72
+ export type ColourVariant = 'filled' | 'tonal' | 'outlined' | 'ghost' | 'link';
73
+
74
+ /** The token of a colour: `default` is the foreground. */
75
+ const token = (color: Color) => (color === 'default' ? 'foreground' : color);
76
+
77
+ /**
78
+ * The classes of a `variant` in a `color`, as material-vue's Button colours
79
+ * them: `NxButton`'s and `NxChip`'s, which each add their own.
80
+ */
81
+ export function colourVariant(variant: ColourVariant, color: Color): string {
82
+ switch (variant) {
83
+ case 'filled':
84
+ return color === 'default'
85
+ ? 'bg-foreground text-background'
86
+ : `bg-${color} text-${color}-foreground`;
87
+ case 'tonal':
88
+ return `bg-${token(color)}-15 text-${token(color)}`;
89
+ case 'outlined':
90
+ return `border border-solid border-${token(color)}-50 text-${token(color)}`;
91
+ case 'ghost':
92
+ return 'text-foreground';
93
+ case 'link':
94
+ return `text-${token(color)}`;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * The components a slot holds, out of any `v-for` fragment, without its text
100
+ * or comments: what `NxAvatarGroup` and `NxSteps` lay out one by one.
101
+ */
102
+ export function slotComponents(nodes: readonly unknown[] | undefined): VNode[] {
103
+ return (nodes ?? []).flatMap((node) => {
104
+ if (!isVNode(node)) return [];
105
+ if (node.type === Fragment) {
106
+ return slotComponents(Array.isArray(node.children) ? node.children : []);
107
+ }
108
+ return typeof node.type === 'symbol' ? [] : [node];
109
+ });
110
+ }
111
+
112
+ /**
113
+ * `t(key, args)` when `@nxgt/mail-i18n` is listed, else `fallback`: a shared
114
+ * message a component writes, in English without the i18n plugin.
115
+ */
116
+ export function sharedMessage(
117
+ globals: Record<string, unknown>,
118
+ key: string,
119
+ fallback: string,
120
+ args?: Record<string, unknown>,
121
+ ): string {
122
+ return typeof globals.t === 'function'
123
+ ? (globals.t(key, args) as string)
124
+ : fallback;
125
+ }
126
+
127
+ /** Which way a figure moved, as material-vue's StatCard tones its delta. */
128
+ export type DeltaTone = 'up' | 'down' | 'neutral';
129
+
130
+ /** A tone's arrow and colour. */
131
+ const DELTA_LOOK: Record<DeltaTone, { glyph: string; colour: string }> = {
132
+ up: { glyph: '\u25B2', colour: 'text-success' },
133
+ down: { glyph: '\u25BC', colour: 'text-error' },
134
+ neutral: { glyph: '\u2013', colour: 'text-muted-foreground' },
135
+ };
136
+
137
+ /**
138
+ * A delta as material-vue's `formatStatDelta` writes it — `+12`, `-3`, `0` —
139
+ * with its tone, a glyph for its arrow (an e-mail has no icon font), and its
140
+ * colour. A string is written as given, `neutral` unless `tone` says.
141
+ */
142
+ export function deltaOf(
143
+ delta: number | string,
144
+ tone?: DeltaTone,
145
+ ): { label: string; tone: DeltaTone; glyph: string; colour: string } {
146
+ const resolved: { label: string; tone: DeltaTone } =
147
+ typeof delta === 'string'
148
+ ? { label: delta, tone: tone ?? 'neutral' }
149
+ : delta > 0
150
+ ? { label: `+${delta}`, tone: tone ?? 'up' }
151
+ : delta < 0
152
+ ? { label: `${delta}`, tone: tone ?? 'down' }
153
+ : { label: '0', tone: tone ?? 'neutral' };
154
+ return { ...resolved, ...DELTA_LOOK[resolved.tone] };
155
+ }
156
+
157
+ /** material-vue's small uppercase label over a figure or a list. */
158
+ export const EYEBROW =
159
+ 'm-0 text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground';
160
+
161
+ /** A link of `NxSeeAlso`, as material-vue's. */
162
+ export interface SeeAlsoItem {
163
+ readonly id?: string;
164
+ readonly title: string;
165
+ readonly href: string;
166
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The messages the layouts and templates share, in `en` and `fr`, for
3
+ * `@nxgt/mail-i18n`:
4
+ *
5
+ * ```ts
6
+ * i18n({ locales: ['en', 'fr'], catalogues: [uiCatalogues] })
7
+ * ```
8
+ *
9
+ * The project's `locales/<locale>.json` overrides any of them key by key. A
10
+ * project in another locale writes the `common` keys itself.
11
+ */
12
+ export declare const uiCatalogues: {
13
+ readonly en: {
14
+ readonly common: {
15
+ readonly greeting: "Hello {name},";
16
+ readonly avatarGroup: {
17
+ readonly more: "{count, plural, other {# more}}";
18
+ };
19
+ readonly timeline: {
20
+ readonly empty: "No activity yet";
21
+ };
22
+ readonly metrics: {
23
+ readonly ofTarget: "of {target}";
24
+ readonly thisPeriod: "This period";
25
+ readonly lastPeriod: "Last period";
26
+ };
27
+ readonly seeAlso: "See also";
28
+ readonly footer: {
29
+ readonly why: "You received this e-mail because you have an account with {brand}.";
30
+ readonly ignore: "If you did not ask for this, you can ignore this e-mail.";
31
+ };
32
+ };
33
+ };
34
+ readonly fr: {
35
+ readonly common: {
36
+ readonly greeting: "Bonjour {name},";
37
+ readonly avatarGroup: {
38
+ readonly more: "{count, plural, one {# autre} other {# autres}}";
39
+ };
40
+ readonly timeline: {
41
+ readonly empty: "Aucune activité pour le moment";
42
+ };
43
+ readonly metrics: {
44
+ readonly ofTarget: "sur {target}";
45
+ readonly thisPeriod: "Cette période";
46
+ readonly lastPeriod: "Période précédente";
47
+ };
48
+ readonly seeAlso: "Voir aussi";
49
+ readonly footer: {
50
+ readonly why: "Vous recevez cet e-mail parce que vous avez un compte chez {brand}.";
51
+ readonly ignore: "Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet e-mail.";
52
+ };
53
+ };
54
+ };
55
+ };
56
+ //# sourceMappingURL=catalogues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogues.d.ts","sourceRoot":"","sources":["../src/catalogues.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCf,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `@nxgt/mail-ui` — e-mail components in the style of `@nxgt/material-vue`,
3
+ * for a Maizzle project.
4
+ *
5
+ * ```ts
6
+ * // maizzle.config.ts
7
+ * import { defineMailConfig } from '@nxgt/mail-config';
8
+ * import { i18n } from '@nxgt/mail-i18n';
9
+ * import { ui, uiCatalogues } from '@nxgt/mail-ui';
10
+ *
11
+ * export default defineMailConfig({
12
+ * plugins: [
13
+ * ui({ brand: { name: 'Acme', url: 'https://acme.example' } }),
14
+ * i18n({ locales: ['en', 'fr'], catalogues: [uiCatalogues] }),
15
+ * ],
16
+ * });
17
+ * ```
18
+ *
19
+ * A template writes `<NxLayout>`, `<NxButton href="…">`, `<NxCard>`, … with
20
+ * material-vue's variants, colours and sizes, rendered with tables and
21
+ * inlined styles.
22
+ */
23
+ import './vue';
24
+ export { uiCatalogues } from './catalogues';
25
+ export { type Brand, COMPONENTS_DIR, UI_CONTEXT, type UiContext, type UiOptions, ui, } from './plugin';
26
+ export { THEME_FILE } from './theme';
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,OAAO,CAAC;AAEf,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACN,KAAK,KAAK,EACV,cAAc,EACd,UAAU,EACV,KAAK,SAAS,EACd,KAAK,SAAS,EACd,EAAE,GACF,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,214 @@
1
+ // src/catalogues.ts
2
+ var uiCatalogues = {
3
+ en: {
4
+ common: {
5
+ greeting: "Hello {name},",
6
+ avatarGroup: { more: "{count, plural, other {# more}}" },
7
+ timeline: { empty: "No activity yet" },
8
+ metrics: {
9
+ ofTarget: "of {target}",
10
+ thisPeriod: "This period",
11
+ lastPeriod: "Last period"
12
+ },
13
+ seeAlso: "See also",
14
+ footer: {
15
+ why: "You received this e-mail because you have an account with {brand}.",
16
+ ignore: "If you did not ask for this, you can ignore this e-mail."
17
+ }
18
+ }
19
+ },
20
+ fr: {
21
+ common: {
22
+ greeting: "Bonjour {name},",
23
+ avatarGroup: { more: "{count, plural, one {# autre} other {# autres}}" },
24
+ timeline: { empty: "Aucune activité pour le moment" },
25
+ metrics: {
26
+ ofTarget: "sur {target}",
27
+ thisPeriod: "Cette période",
28
+ lastPeriod: "Période précédente"
29
+ },
30
+ seeAlso: "Voir aussi",
31
+ footer: {
32
+ why: "Vous recevez cet e-mail parce que vous avez un compte chez {brand}.",
33
+ ignore: "Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet e-mail."
34
+ }
35
+ }
36
+ }
37
+ };
38
+ // src/plugin.ts
39
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
40
+ import { dirname as dirname2, resolve as resolve2 } from "node:path";
41
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
42
+ import { isMainThread } from "node:worker_threads";
43
+ import { defineMailPlugin } from "@nxgt/mail-config";
44
+
45
+ // src/packaged.ts
46
+ import { existsSync, readdirSync } from "node:fs";
47
+ import { dirname, join, resolve } from "node:path";
48
+ import { fileURLToPath } from "node:url";
49
+ import Components from "unplugin-vue-components/vite";
50
+ var PACKAGED = /[\\/]node_modules[\\/].*\.vue(\?vue.*)?$/;
51
+ var MAIZZLE = /[\\/]node_modules[\\/]@maizzle[\\/]framework[\\/]/;
52
+ function maizzleComponentsDir(from) {
53
+ for (let dir = from;; dir = dirname(dir)) {
54
+ const candidate = join(dir, "node_modules/@maizzle/framework/dist/components");
55
+ if (existsSync(candidate))
56
+ return candidate;
57
+ if (dirname(dir) === dir) {
58
+ throw new Error("ui: @maizzle/framework is not installed beside @nxgt/mail-ui");
59
+ }
60
+ }
61
+ }
62
+ var pascalCase = (name) => name.replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (c) => c.toUpperCase());
63
+ function componentName(file, prefix) {
64
+ const name = pascalCase(file.slice(0, -".vue".length));
65
+ if (prefix === undefined)
66
+ return name;
67
+ return prefix + (name.startsWith(prefix) ? name.slice(prefix.length) : name);
68
+ }
69
+ function fileForTag(folders, tag) {
70
+ for (const { path, prefix } of folders) {
71
+ if (!existsSync(path))
72
+ continue;
73
+ const file = readdirSync(path).filter((entry) => entry.endsWith(".vue")).sort().find((entry) => componentName(entry, prefix) === tag);
74
+ if (file !== undefined)
75
+ return join(path, file);
76
+ }
77
+ return;
78
+ }
79
+ function packagedComponents(ours) {
80
+ const builtins = maizzleComponentsDir(dirname(fileURLToPath(import.meta.url)));
81
+ let root = process.cwd();
82
+ return [
83
+ {
84
+ name: "nxgt:mail-ui:root",
85
+ configResolved(config) {
86
+ root = config.root;
87
+ }
88
+ },
89
+ Components({
90
+ include: [PACKAGED],
91
+ exclude: [MAIZZLE],
92
+ dirs: [],
93
+ resolvers: [
94
+ (name) => fileForTag([{ path: resolve(root, "components") }, ours, { path: builtins }], name)
95
+ ],
96
+ dts: false
97
+ })
98
+ ];
99
+ }
100
+
101
+ // src/theme.ts
102
+ import { readFileSync } from "node:fs";
103
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
104
+ var THEME_FILE = fileURLToPath2(new URL("../theme.css", import.meta.url));
105
+ var TOKEN = /^[a-z][a-z0-9-]*$/;
106
+ var VALUE = /^(?!.*(?:\/\*|\*\/))[^;{}<>"'\\\r\n]+$/;
107
+ function declaredTokens(css) {
108
+ return new Set(Array.from(css.matchAll(/^\s*--([a-z][a-z0-9-]*)\s*:/gm), (m) => m[1]));
109
+ }
110
+ function themeCss(theme) {
111
+ const css = readFileSync(THEME_FILE, "utf8");
112
+ const tokens = declaredTokens(css);
113
+ const overrides = [];
114
+ for (const [token, value] of Object.entries(theme)) {
115
+ if (!TOKEN.test(token) || !tokens.has(token)) {
116
+ throw new TypeError(`ui: theme.${token} is not a token of the theme — name one of theme.css without its --, as color-primary`);
117
+ }
118
+ if (typeof value !== "string" || !VALUE.test(value.trim())) {
119
+ throw new TypeError(`ui: theme.${token} must be a CSS value, as #0f766e or 8px`);
120
+ }
121
+ overrides.push(` --${token}: ${value.trim()};`);
122
+ }
123
+ return overrides.length === 0 ? css : `${css}
124
+ @theme {
125
+ ${overrides.join(`
126
+ `)}
127
+ }
128
+ `;
129
+ }
130
+
131
+ // src/plugin.ts
132
+ var UI_CONTEXT = "nxgt:mail-ui";
133
+ var TYPES_FILE = ".maizzle/nxgt-mail-ui.d.ts";
134
+ var TYPES_SOURCE = [
135
+ "// Generated by @nxgt/mail-ui each time the config loads. Never edited, never committed:",
136
+ "// it gives the templates `brand`.",
137
+ "import type {} from '@nxgt/mail-ui';",
138
+ ""
139
+ ].join(`
140
+ `);
141
+ function writeTypes() {
142
+ const file = resolve2(process.cwd(), TYPES_FILE);
143
+ if (existsSync2(file) && readFileSync2(file, "utf8") === TYPES_SOURCE)
144
+ return;
145
+ mkdirSync(dirname2(file), { recursive: true });
146
+ writeFileSync(file, TYPES_SOURCE);
147
+ }
148
+ var COMPONENTS_DIR = fileURLToPath3(new URL("../components", import.meta.url));
149
+ var COMPONENTS = { path: COMPONENTS_DIR, prefix: "Nx" };
150
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
151
+ var isAbsoluteUrl = (value) => typeof value === "string" && /^https?:\/\/\S+$/.test(value);
152
+ function checkBrand(brand) {
153
+ if (!isObject(brand)) {
154
+ throw new TypeError("ui: brand must be an object, as { name: 'Acme', url: 'https://acme.example' }");
155
+ }
156
+ if (typeof brand.name !== "string" || brand.name.trim() === "") {
157
+ throw new TypeError("ui: brand.name must be the name the e-mails show");
158
+ }
159
+ if (brand.url !== undefined && !isAbsoluteUrl(brand.url)) {
160
+ throw new TypeError("ui: brand.url must be an absolute http(s) URL");
161
+ }
162
+ const { logo } = brand;
163
+ if (logo === undefined)
164
+ return;
165
+ if (!isObject(logo) || !isAbsoluteUrl(logo.src)) {
166
+ throw new TypeError("ui: brand.logo.src must be an absolute http(s) URL — a mail client loads nothing relative");
167
+ }
168
+ if (logo.width !== undefined && (typeof logo.width !== "number" || !Number.isInteger(logo.width) || logo.width <= 0)) {
169
+ throw new TypeError("ui: brand.logo.width must be a width in pixels");
170
+ }
171
+ if (logo.alt !== undefined && typeof logo.alt !== "string") {
172
+ throw new TypeError("ui: brand.logo.alt must be a string");
173
+ }
174
+ }
175
+ function ui(options) {
176
+ if (!isObject(options)) {
177
+ throw new TypeError("ui: options must be an object, as { brand: { name: 'Acme' } }");
178
+ }
179
+ checkBrand(options.brand);
180
+ if (options.theme !== undefined && !isObject(options.theme)) {
181
+ throw new TypeError("ui: theme must be an object of tokens, as { 'color-primary': '#0f766e' }");
182
+ }
183
+ const brand = Object.freeze({
184
+ ...options.brand,
185
+ ...options.brand.logo && {
186
+ logo: Object.freeze({ ...options.brand.logo })
187
+ }
188
+ });
189
+ const context = Object.freeze({
190
+ brand,
191
+ css: themeCss(options.theme ?? {})
192
+ });
193
+ if (isMainThread)
194
+ writeTypes();
195
+ return defineMailPlugin({
196
+ name: "ui",
197
+ components: { source: [COMPONENTS] },
198
+ vite: { plugins: packagedComponents(COMPONENTS) },
199
+ vue: {
200
+ globalProperties: { brand },
201
+ plugins: [{ install: (app) => app.provide(UI_CONTEXT, context) }]
202
+ }
203
+ });
204
+ }
205
+ export {
206
+ COMPONENTS_DIR,
207
+ THEME_FILE,
208
+ UI_CONTEXT,
209
+ ui,
210
+ uiCatalogues
211
+ };
212
+
213
+ //# debugId=659CA3FBCE3C1FE364756E2164756E21
214
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/catalogues.ts", "../src/plugin.ts", "../src/packaged.ts", "../src/theme.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * The messages the layouts and templates share, in `en` and `fr`, for\n * `@nxgt/mail-i18n`:\n *\n * ```ts\n * i18n({ locales: ['en', 'fr'], catalogues: [uiCatalogues] })\n * ```\n *\n * The project's `locales/<locale>.json` overrides any of them key by key. A\n * project in another locale writes the `common` keys itself.\n */\nexport const uiCatalogues = {\n\ten: {\n\t\tcommon: {\n\t\t\tgreeting: 'Hello {name},',\n\t\t\tavatarGroup: { more: '{count, plural, other {# more}}' },\n\t\t\ttimeline: { empty: 'No activity yet' },\n\t\t\tmetrics: {\n\t\t\t\tofTarget: 'of {target}',\n\t\t\t\tthisPeriod: 'This period',\n\t\t\t\tlastPeriod: 'Last period',\n\t\t\t},\n\t\t\tseeAlso: 'See also',\n\t\t\tfooter: {\n\t\t\t\twhy: 'You received this e-mail because you have an account with {brand}.',\n\t\t\t\tignore: 'If you did not ask for this, you can ignore this e-mail.',\n\t\t\t},\n\t\t},\n\t},\n\tfr: {\n\t\tcommon: {\n\t\t\tgreeting: 'Bonjour {name},',\n\t\t\tavatarGroup: { more: '{count, plural, one {# autre} other {# autres}}' },\n\t\t\ttimeline: { empty: 'Aucune activité pour le moment' },\n\t\t\tmetrics: {\n\t\t\t\tofTarget: 'sur {target}',\n\t\t\t\tthisPeriod: 'Cette période',\n\t\t\t\tlastPeriod: 'Période précédente',\n\t\t\t},\n\t\t\tseeAlso: 'Voir aussi',\n\t\t\tfooter: {\n\t\t\t\twhy: 'Vous recevez cet e-mail parce que vous avez un compte chez {brand}.',\n\t\t\t\tignore:\n\t\t\t\t\t\"Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet e-mail.\",\n\t\t\t},\n\t\t},\n\t},\n} as const;\n",
6
+ "import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { isMainThread } from 'node:worker_threads';\nimport { defineMailPlugin, type MailPlugin } from '@nxgt/mail-config';\nimport { packagedComponents } from './packaged';\nimport { themeCss } from './theme';\n\n/** Who sends the e-mail: the layout's header and footer. */\nexport interface Brand {\n\t/** The name the header shows without a logo, and the footer always. */\n\treadonly name: string;\n\t/** Where the header and the footer link to, as `https://acme.example`. */\n\treadonly url?: string;\n\t/** The header's image, by absolute URL: a mail client loads nothing relative. */\n\treadonly logo?: {\n\t\treadonly src: string;\n\t\t/** In pixels. Default 120. */\n\t\treadonly width?: number;\n\t\t/** Default the brand's name. */\n\t\treadonly alt?: string;\n\t};\n}\n\nexport interface UiOptions {\n\treadonly brand: Brand;\n\t/**\n\t * Tokens of `theme.css` to override, named without their `--`:\n\t * `{ 'color-primary': '#0f766e', 'radius-lg': '4px' }`. The tints of a\n\t * colour follow it.\n\t */\n\treadonly theme?: Readonly<Record<string, string>>;\n}\n\n/** What `NxLayout` injects: the brand and the theme's CSS. */\nexport interface UiContext {\n\treadonly brand: Brand;\n\treadonly css: string;\n}\n\n/** The `provide` key the components read the {@link UiContext} from. */\nexport const UI_CONTEXT = 'nxgt:mail-ui';\n\n/**\n * Where the template types go, under the project: `.maizzle/*.d.ts` is in the\n * starter's `tsconfig.json`, and `maizzle.config.ts` — which imports this\n * package — is not, so an editor would not know `brand` without it.\n */\nexport const TYPES_FILE = '.maizzle/nxgt-mail-ui.d.ts';\n\nconst TYPES_SOURCE = [\n\t'// Generated by @nxgt/mail-ui each time the config loads. Never edited, never committed:',\n\t'// it gives the templates `brand`.',\n\t\"import type {} from '@nxgt/mail-ui';\",\n\t'',\n].join('\\n');\n\n/** Writes the template types, only when they changed. */\nfunction writeTypes(): void {\n\tconst file = resolve(process.cwd(), TYPES_FILE);\n\tif (existsSync(file) && readFileSync(file, 'utf8') === TYPES_SOURCE) return;\n\tmkdirSync(dirname(file), { recursive: true });\n\twriteFileSync(file, TYPES_SOURCE);\n}\n\n/** The components, beside `src/` and `dist/` in the package. */\nexport const COMPONENTS_DIR = fileURLToPath(\n\tnew URL('../components', import.meta.url),\n);\n\n/**\n * Our components, under the prefix `Nx`: `card-header.vue` is\n * `<NxCardHeader>`, and Maizzle's own (`<Button>`) stay available.\n */\nconst COMPONENTS = { path: COMPONENTS_DIR, prefix: 'Nx' } as const;\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isAbsoluteUrl = (value: unknown): value is string =>\n\ttypeof value === 'string' && /^https?:\\/\\/\\S+$/.test(value);\n\nfunction checkBrand(brand: unknown): asserts brand is Brand {\n\tif (!isObject(brand)) {\n\t\tthrow new TypeError(\n\t\t\t\"ui: brand must be an object, as { name: 'Acme', url: 'https://acme.example' }\",\n\t\t);\n\t}\n\tif (typeof brand.name !== 'string' || brand.name.trim() === '') {\n\t\tthrow new TypeError('ui: brand.name must be the name the e-mails show');\n\t}\n\tif (brand.url !== undefined && !isAbsoluteUrl(brand.url)) {\n\t\tthrow new TypeError('ui: brand.url must be an absolute http(s) URL');\n\t}\n\tconst { logo } = brand;\n\tif (logo === undefined) return;\n\tif (!isObject(logo) || !isAbsoluteUrl(logo.src)) {\n\t\tthrow new TypeError(\n\t\t\t'ui: brand.logo.src must be an absolute http(s) URL — a mail client loads nothing relative',\n\t\t);\n\t}\n\tif (\n\t\tlogo.width !== undefined &&\n\t\t(typeof logo.width !== 'number' ||\n\t\t\t!Number.isInteger(logo.width) ||\n\t\t\tlogo.width <= 0)\n\t) {\n\t\tthrow new TypeError('ui: brand.logo.width must be a width in pixels');\n\t}\n\tif (logo.alt !== undefined && typeof logo.alt !== 'string') {\n\t\tthrow new TypeError('ui: brand.logo.alt must be a string');\n\t}\n}\n\n/**\n * The components of `@nxgt/mail-ui`, for `defineMailConfig`:\n *\n * ```ts\n * defineMailConfig({\n * plugins: [ui({ brand: { name: 'Acme' } }), i18n({ locales: ['en', 'fr'] })],\n * });\n * ```\n *\n * It registers `NxLayout`, `NxButton`, … — a project's own\n * `components/nx-button.vue` replaces ours — gives every template `brand`,\n * and themes the layout with `theme.css` and the `theme` overrides.\n */\nexport function ui(options: UiOptions): MailPlugin {\n\tif (!isObject(options)) {\n\t\tthrow new TypeError(\n\t\t\t\"ui: options must be an object, as { brand: { name: 'Acme' } }\",\n\t\t);\n\t}\n\tcheckBrand(options.brand);\n\tif (options.theme !== undefined && !isObject(options.theme)) {\n\t\tthrow new TypeError(\n\t\t\t\"ui: theme must be an object of tokens, as { 'color-primary': '#0f766e' }\",\n\t\t);\n\t}\n\tconst brand: Brand = Object.freeze({\n\t\t...options.brand,\n\t\t...(options.brand.logo && {\n\t\t\tlogo: Object.freeze({ ...options.brand.logo }),\n\t\t}),\n\t});\n\tconst context: UiContext = Object.freeze({\n\t\tbrand,\n\t\tcss: themeCss(options.theme ?? {}),\n\t});\n\t// A parallel build loads the config in each worker: only the main thread writes.\n\tif (isMainThread) writeTypes();\n\treturn defineMailPlugin({\n\t\tname: 'ui',\n\t\tcomponents: { source: [COMPONENTS] },\n\t\tvite: { plugins: packagedComponents(COMPONENTS) },\n\t\tvue: {\n\t\t\tglobalProperties: { brand },\n\t\t\tplugins: [{ install: (app) => app.provide(UI_CONTEXT, context) }],\n\t\t},\n\t});\n}\n",
7
+ "import { existsSync, readdirSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { MaizzleConfig } from '@maizzle/framework';\nimport Components from 'unplugin-vue-components/vite';\n\n/** A Vite plugin, as Maizzle's `vite.plugins` takes one. */\ntype VitePlugins = NonNullable<NonNullable<MaizzleConfig['vite']>['plugins']>;\n\n/** A `.vue` file an installed package ships: ours, or a preset's. */\nconst PACKAGED = /[\\\\/]node_modules[\\\\/].*\\.vue(\\?vue.*)?$/;\n\n/** Maizzle's built-ins resolve their own imports; leave them be. */\nconst MAIZZLE = /[\\\\/]node_modules[\\\\/]@maizzle[\\\\/]framework[\\\\/]/;\n\n/**\n * The folder of `@maizzle/framework`'s built-in components, found the way\n * Node finds the package: up from here, through each `node_modules`. The\n * package exports no subpath to ask it with.\n */\nfunction maizzleComponentsDir(from: string): string {\n\tfor (let dir = from; ; dir = dirname(dir)) {\n\t\tconst candidate = join(\n\t\t\tdir,\n\t\t\t'node_modules/@maizzle/framework/dist/components',\n\t\t);\n\t\tif (existsSync(candidate)) return candidate;\n\t\tif (dirname(dir) === dir) {\n\t\t\tthrow new Error(\n\t\t\t\t'ui: @maizzle/framework is not installed beside @nxgt/mail-ui',\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * `card-header` in PascalCase, `CardHeader`, as Maizzle writes a file's name.\n * A copy of its `pascalCase` (`@maizzle/framework/dist/utils/componentSources.js`),\n * which the package does not export: change them together.\n */\nconst pascalCase = (name: string): string =>\n\tname\n\t\t.replace(/[-_\\s]+(.)/g, (_, c: string) => c.toUpperCase())\n\t\t.replace(/^(.)/, (c) => c.toUpperCase());\n\n/** A folder of components, and the prefix Maizzle gives their names, if any. */\nexport interface ComponentsFolder {\n\treadonly path: string;\n\treadonly prefix?: string;\n}\n\n/**\n * The name Maizzle gives the component of a file at the top of a folder, as\n * its `componentNameFromPath`: `card-header.vue` under the prefix `Nx` is\n * `NxCardHeader`, and so is `nx-card-header.vue` — a prefix the name already\n * starts with is not repeated. Without a prefix, `nx-badge.vue` is `NxBadge`.\n */\nfunction componentName(file: string, prefix: string | undefined): string {\n\tconst name = pascalCase(file.slice(0, -'.vue'.length));\n\tif (prefix === undefined) return name;\n\treturn prefix + (name.startsWith(prefix) ? name.slice(prefix.length) : name);\n}\n\n/**\n * The first `.vue` file at the top of `folders`, in order, that Maizzle names\n * `tag` — `button.vue` under the prefix `Nx`, or a project's `nx-button.vue`\n * or `NxButton.vue`, for `<NxButton>`. Named, not guessed: `2fa.vue` under\n * `Nx` is `<Nx2fa>`, which no case conversion of the tag gives back. A missing\n * folder is skipped.\n */\nexport function fileForTag(\n\tfolders: readonly ComponentsFolder[],\n\ttag: string,\n): string | undefined {\n\tfor (const { path, prefix } of folders) {\n\t\tif (!existsSync(path)) continue;\n\t\t// Sorted, so that of two files Maizzle would give the same name, as\n\t\t// `nx-badge.vue` and `NxBadge.vue`, the same one wins on every machine.\n\t\tconst file = readdirSync(path)\n\t\t\t.filter((entry) => entry.endsWith('.vue'))\n\t\t\t.sort()\n\t\t\t.find((entry) => componentName(entry, prefix) === tag);\n\t\tif (file !== undefined) return join(path, file);\n\t}\n\treturn undefined;\n}\n\n/**\n * Maizzle resolves the tags of a template (`<NxButton>`, `<Container>`) with\n * unplugin-vue-components, which skips every file under `node_modules` — so a\n * component or a template installed from npm would render empty, and the\n * build would pass. These plugins resolve the tags of those files the way\n * Maizzle would: the project's `components/` first, then ours, then Maizzle's\n * built-ins.\n */\nexport function packagedComponents(ours: ComponentsFolder): VitePlugins {\n\tconst builtins = maizzleComponentsDir(\n\t\tdirname(fileURLToPath(import.meta.url)),\n\t);\n\tlet root = process.cwd();\n\treturn [\n\t\t{\n\t\t\tname: 'nxgt:mail-ui:root',\n\t\t\tconfigResolved(config: { readonly root: string }) {\n\t\t\t\troot = config.root;\n\t\t\t},\n\t\t},\n\t\tComponents({\n\t\t\tinclude: [PACKAGED],\n\t\t\texclude: [MAIZZLE],\n\t\t\tdirs: [],\n\t\t\tresolvers: [\n\t\t\t\t(name) =>\n\t\t\t\t\tfileForTag(\n\t\t\t\t\t\t[{ path: resolve(root, 'components') }, ours, { path: builtins }],\n\t\t\t\t\t\tname,\n\t\t\t\t\t),\n\t\t\t],\n\t\t\tdts: false,\n\t\t}),\n\t];\n}\n",
8
+ "import { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\n\n/** `theme.css`, beside `src/` and `dist/` in the package. */\nexport const THEME_FILE = fileURLToPath(\n\tnew URL('../theme.css', import.meta.url),\n);\n\n/** A token as `theme` names it: `color-primary`, `radius-lg`. */\nconst TOKEN = /^[a-z][a-z0-9-]*$/;\n\n/**\n * A value that stays inside its declaration: no `;`, brace, angle bracket,\n * quote, backslash, comment or line break.\n */\nconst VALUE = /^(?!.*(?:\\/\\*|\\*\\/))[^;{}<>\"'\\\\\\r\\n]+$/;\n\n/** The tokens `css` declares, without their `--`: `color-primary`, … */\nexport function declaredTokens(css: string): ReadonlySet<string> {\n\treturn new Set(\n\t\tArray.from(\n\t\t\tcss.matchAll(/^\\s*--([a-z][a-z0-9-]*)\\s*:/gm),\n\t\t\t(m) => m[1] as string,\n\t\t),\n\t);\n}\n\n/**\n * The CSS a layout puts under `@import \"@maizzle/tailwindcss\"`: the theme,\n * then an `@theme` block of the project's overrides, which wins. A token the\n * theme does not declare, or a value that is not one, **throws**.\n */\nexport function themeCss(theme: Readonly<Record<string, string>>): string {\n\tconst css = readFileSync(THEME_FILE, 'utf8');\n\tconst tokens = declaredTokens(css);\n\tconst overrides: string[] = [];\n\tfor (const [token, value] of Object.entries(theme)) {\n\t\tif (!TOKEN.test(token) || !tokens.has(token)) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`ui: theme.${token} is not a token of the theme — name one of theme.css without its --, as color-primary`,\n\t\t\t);\n\t\t}\n\t\tif (typeof value !== 'string' || !VALUE.test(value.trim())) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`ui: theme.${token} must be a CSS value, as #0f766e or 8px`,\n\t\t\t);\n\t\t}\n\t\toverrides.push(`\\t--${token}: ${value.trim()};`);\n\t}\n\treturn overrides.length === 0\n\t\t? css\n\t\t: `${css}\\n@theme {\\n${overrides.join('\\n')}\\n}\\n`;\n}\n"
9
+ ],
10
+ "mappings": "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",
11
+ "debugId": "659CA3FBCE3C1FE364756E2164756E21",
12
+ "names": []
13
+ }