@mimi-ng/cli 0.4.0 → 0.5.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/README.md +29 -7
- package/dist/init/index.js +8 -0
- package/dist/registry.js +4 -1
- package/dist/registry.json +87 -1
- package/dist/templates/blocks/login-01/login-01.ts +12 -3
- package/dist/templates/blocks/login-02/login-02.ts +12 -3
- package/dist/templates/blocks/otp-01/otp-01.ts +12 -3
- package/dist/templates/blocks/pricing-01/pricing-01.ts +1 -1
- package/dist/templates/blocks/settings-01/settings-01.ts +13 -3
- package/dist/templates/blocks/signup-01/signup-01.ts +12 -2
- package/dist/templates/blocks/table-01/index.ts +1 -0
- package/dist/templates/blocks/table-01/table-01.html +214 -0
- package/dist/templates/blocks/table-01/table-01.ts +266 -0
- package/dist/templates/components/checkbox/checkbox.ts +14 -5
- package/dist/templates/components/command/command-texts.ts +65 -0
- package/dist/templates/components/command/command.ts +553 -0
- package/dist/templates/components/command/index.ts +13 -0
- package/dist/templates/components/data-table/data-table-texts.ts +110 -0
- package/dist/templates/components/data-table/data-table.ts +1153 -0
- package/dist/templates/components/data-table/index.ts +18 -0
- package/dist/templates/components/sortable-list/index.ts +9 -0
- package/dist/templates/components/sortable-list/sortable-list-texts.ts +71 -0
- package/dist/templates/components/sortable-list/sortable-list.ts +510 -0
- package/dist/templates/components/tree/index.ts +9 -0
- package/dist/templates/components/tree/tree-texts.ts +45 -0
- package/dist/templates/components/tree/tree.ts +730 -0
- package/dist/templates/theme/provider.ts +4 -0
- package/dist/templates/theme/types.ts +84 -0
- package/dist/ui/index.js +232 -40
- package/dist/ui/schema.json +30 -6
- package/dist/utils/pages.js +138 -0
- package/dist/utils/project.js +33 -9
- package/dist/utils/tailwind.js +15 -11
- package/dist/utils/write-files.js +32 -7
- package/package.json +1 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
LOCALE_ID,
|
|
5
|
+
computed,
|
|
6
|
+
inject,
|
|
7
|
+
input,
|
|
8
|
+
model,
|
|
9
|
+
output,
|
|
10
|
+
} from '@angular/core';
|
|
11
|
+
import { MimiBadge, type BadgeTone } from '@/components/ui/badge';
|
|
12
|
+
import { MimiButton } from '@/components/ui/button';
|
|
13
|
+
import { MimiCheckbox } from '@/components/ui/checkbox';
|
|
14
|
+
import {
|
|
15
|
+
MimiDataTableImports,
|
|
16
|
+
type MimiTableColumn,
|
|
17
|
+
type MimiTableFilterOption,
|
|
18
|
+
} from '@/components/ui/data-table';
|
|
19
|
+
import { MimiInput } from '@/components/ui/input';
|
|
20
|
+
import { cn } from '@/components/ui/utils/cn';
|
|
21
|
+
|
|
22
|
+
export type Table01Status = 'active' | 'trial' | 'past-due' | 'canceled';
|
|
23
|
+
|
|
24
|
+
export interface Table01Customer {
|
|
25
|
+
/** Identificador estable (sale en `deleted` y `exported`). */
|
|
26
|
+
id: number;
|
|
27
|
+
name: string;
|
|
28
|
+
email: string;
|
|
29
|
+
status: Table01Status;
|
|
30
|
+
plan: string;
|
|
31
|
+
/** Ingreso mensual recurrente. */
|
|
32
|
+
mrr: number;
|
|
33
|
+
users: number;
|
|
34
|
+
country: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Cuenta de cada estado en el resumen y en las píldoras de la lista móvil. */
|
|
38
|
+
const STATUSES: { id: Table01Status; label: string; plural: string; tone: BadgeTone }[] = [
|
|
39
|
+
{ id: 'active', label: 'Active', plural: 'Active', tone: 'success' },
|
|
40
|
+
{ id: 'trial', label: 'Trial', plural: 'Trial', tone: 'info' },
|
|
41
|
+
{ id: 'past-due', label: 'Past due', plural: 'Past due', tone: 'warning' },
|
|
42
|
+
{ id: 'canceled', label: 'Canceled', plural: 'Canceled', tone: 'danger' },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const NAMES = [
|
|
46
|
+
'Hotel Brisa',
|
|
47
|
+
'Café Altura',
|
|
48
|
+
'Taller Motor',
|
|
49
|
+
'Grupo Talento',
|
|
50
|
+
'Óptica Clara',
|
|
51
|
+
'Editorial Faro',
|
|
52
|
+
'Clínica Sol',
|
|
53
|
+
'Panadería Trigo',
|
|
54
|
+
'Estudio Norte',
|
|
55
|
+
];
|
|
56
|
+
const PLANS = ['Starter', 'Team', 'Enterprise'];
|
|
57
|
+
const COUNTRIES = ['Colombia', 'México', 'Chile', 'Perú'];
|
|
58
|
+
const SEQUENCE: Table01Status[] = ['active', 'trial', 'active', 'past-due', 'active', 'canceled'];
|
|
59
|
+
|
|
60
|
+
/** 36 clientes de ejemplo, siempre los mismos, para que la pantalla funcione sin datos. */
|
|
61
|
+
export const TABLE_01_CUSTOMERS: Table01Customer[] = Array.from({ length: 36 }, (_, i) => {
|
|
62
|
+
const name = `${NAMES[i % NAMES.length]} ${Math.floor(i / NAMES.length) + 1}`;
|
|
63
|
+
return {
|
|
64
|
+
id: i + 1,
|
|
65
|
+
name,
|
|
66
|
+
email: `hello@${name
|
|
67
|
+
.normalize('NFD')
|
|
68
|
+
.replace(/[̀-ͯ]/g, '')
|
|
69
|
+
.toLowerCase()
|
|
70
|
+
.replace(/[^a-z0-9]+/g, '-')}.com`,
|
|
71
|
+
status: SEQUENCE[i % SEQUENCE.length],
|
|
72
|
+
plan: PLANS[i % PLANS.length],
|
|
73
|
+
mrr: 300 + ((i * 7919) % 180) * 40,
|
|
74
|
+
users: 2 + ((i * 13) % 60),
|
|
75
|
+
country: COUNTRIES[i % COUNTRIES.length],
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* table-01 (Block de Mimi): la pantalla «Clientes». En escritorio, un resumen por estado y la
|
|
81
|
+
* Data Table de Mimi (buscar, filtrar por estado o plan, ordenar, paginar y elegir filas con sus
|
|
82
|
+
* acciones); en móvil, una lista de tarjetas con búsqueda, píldoras de estado y una barra de
|
|
83
|
+
* acciones. Diseño: docs/design/Block table-01.dc.html.
|
|
84
|
+
*
|
|
85
|
+
* Es una página: `ng g mimi table-01` la genera en tu carpeta de páginas (`aliases.pages` de
|
|
86
|
+
* mimi.json; por defecto, `src/app/pages/table/`, u otro nombre con `--name`) junto con su
|
|
87
|
+
* archivo de rutas (`table.routes.ts`, con la página en carga diferida), y te da la línea para
|
|
88
|
+
* `app.routes.ts`:
|
|
89
|
+
*
|
|
90
|
+
* ```ts
|
|
91
|
+
* { path: 'table', loadChildren: () => import('./pages/table/table.routes').then((m) => m.TABLE_ROUTES) },
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* El archivo es tuyo: da tus clientes con `customers` (sin ellos, los de ejemplo), conecta
|
|
95
|
+
* «New customer», «Import CSV», «Export» y «Delete» a tu servicio con sus salidas, o cambia las
|
|
96
|
+
* columnas. Con muchos clientes, usa la Data Table en modo servidor (`[total]`).
|
|
97
|
+
*
|
|
98
|
+
* ```html
|
|
99
|
+
* <mimi-table-01 [customers]="customers" (deleted)="remove($event)" (newCustomer)="create()" />
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
@Component({
|
|
103
|
+
selector: 'mimi-table-01',
|
|
104
|
+
imports: [MimiBadge, MimiButton, MimiCheckbox, MimiDataTableImports, MimiInput],
|
|
105
|
+
templateUrl: './table-01.html',
|
|
106
|
+
host: { '[class]': 'classes()' },
|
|
107
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
108
|
+
})
|
|
109
|
+
export class Table01 {
|
|
110
|
+
readonly customers = input<readonly Table01Customer[]>(TABLE_01_CUSTOMERS);
|
|
111
|
+
/** Moneda del ingreso mensual (ISO 4217). */
|
|
112
|
+
readonly currency = input('USD');
|
|
113
|
+
/** Idioma de los números. Por defecto, el `LOCALE_ID` de la app. */
|
|
114
|
+
readonly locale = input<string | undefined>(undefined);
|
|
115
|
+
/** Ids de las filas elegidas. Admite `[(selection)]`. */
|
|
116
|
+
readonly selection = model<unknown[]>([]);
|
|
117
|
+
readonly userClass = input('', { alias: 'class' });
|
|
118
|
+
|
|
119
|
+
/** «New customer». */
|
|
120
|
+
readonly newCustomer = output<void>();
|
|
121
|
+
/** «Import CSV» (solo en escritorio). */
|
|
122
|
+
readonly importCsv = output<void>();
|
|
123
|
+
/** «Export» con filas elegidas: sus ids. */
|
|
124
|
+
readonly exported = output<unknown[]>();
|
|
125
|
+
/** «Delete» con filas elegidas: sus ids. La pantalla no las quita: lo haces tú en `customers`. */
|
|
126
|
+
readonly deleted = output<unknown[]>();
|
|
127
|
+
|
|
128
|
+
private readonly appLocale = inject(LOCALE_ID);
|
|
129
|
+
protected readonly statuses = STATUSES;
|
|
130
|
+
|
|
131
|
+
/** La búsqueda y el estado elegido de la lista móvil. */
|
|
132
|
+
protected readonly query = model('');
|
|
133
|
+
protected readonly statusFilter = model<Table01Status | null>(null);
|
|
134
|
+
|
|
135
|
+
private readonly money = computed(
|
|
136
|
+
() =>
|
|
137
|
+
new Intl.NumberFormat(this.locale() ?? this.appLocale, {
|
|
138
|
+
style: 'currency',
|
|
139
|
+
currency: this.currency(),
|
|
140
|
+
maximumFractionDigits: 0,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
protected format(amount: number): string {
|
|
145
|
+
return this.money().format(amount);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** El punto de color de cada estado (clases literales para Tailwind). */
|
|
149
|
+
protected dotClass(tone: BadgeTone): string {
|
|
150
|
+
return (
|
|
151
|
+
{
|
|
152
|
+
success: 'bg-success',
|
|
153
|
+
info: 'bg-info',
|
|
154
|
+
warning: 'bg-warning',
|
|
155
|
+
danger: 'bg-destructive',
|
|
156
|
+
primary: 'bg-primary',
|
|
157
|
+
secondary: 'bg-secondary',
|
|
158
|
+
} satisfies Record<BadgeTone, string>
|
|
159
|
+
)[tone];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
protected statusOf(status: Table01Status) {
|
|
163
|
+
return STATUSES.find((s) => s.id === status)!;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Cuántos clientes hay de cada estado. */
|
|
167
|
+
protected readonly counts = computed(() =>
|
|
168
|
+
STATUSES.map((s) => ({
|
|
169
|
+
...s,
|
|
170
|
+
count: this.customers().filter((c) => c.status === s.id).length,
|
|
171
|
+
})),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
protected readonly subtitle = computed(() => {
|
|
175
|
+
const customers = this.customers();
|
|
176
|
+
const mrr = customers.reduce((sum, c) => sum + c.mrr, 0);
|
|
177
|
+
return `${customers.length} accounts · ${this.format(mrr)} MRR`;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
protected readonly columns = computed<MimiTableColumn<Table01Customer>[]>(() => {
|
|
181
|
+
const options = (values: string[], label: (v: string) => string): MimiTableFilterOption[] =>
|
|
182
|
+
values.map((value) => ({ value, label: label(value) }));
|
|
183
|
+
return [
|
|
184
|
+
{ id: 'name', header: 'Customer', sortable: true, sticky: true, width: '280px' },
|
|
185
|
+
{
|
|
186
|
+
id: 'status',
|
|
187
|
+
header: 'Status',
|
|
188
|
+
width: '130px',
|
|
189
|
+
filterOptions: options(
|
|
190
|
+
STATUSES.map((s) => s.id),
|
|
191
|
+
(id) => this.statusOf(id as Table01Status).label,
|
|
192
|
+
),
|
|
193
|
+
value: (row) => row.status,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'plan',
|
|
197
|
+
header: 'Plan',
|
|
198
|
+
width: '130px',
|
|
199
|
+
filterOptions: options(PLANS, (p) => p),
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: 'mrr',
|
|
203
|
+
header: 'MRR',
|
|
204
|
+
sortable: true,
|
|
205
|
+
align: 'end',
|
|
206
|
+
width: '120px',
|
|
207
|
+
searchable: false,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: 'users',
|
|
211
|
+
header: 'Users',
|
|
212
|
+
sortable: true,
|
|
213
|
+
align: 'end',
|
|
214
|
+
width: '100px',
|
|
215
|
+
searchable: false,
|
|
216
|
+
},
|
|
217
|
+
{ id: 'country', header: 'Country', sortable: true, width: '140px' },
|
|
218
|
+
];
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
/** La lista móvil: la búsqueda (sin tildes) y el estado elegido. */
|
|
222
|
+
protected readonly list = computed(() => {
|
|
223
|
+
const fold = (t: string) => t.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
|
|
224
|
+
const q = fold(this.query().trim());
|
|
225
|
+
const status = this.statusFilter();
|
|
226
|
+
return this.customers().filter(
|
|
227
|
+
(c) => (!status || c.status === status) && (!q || fold(c.name).includes(q)),
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
protected readonly listIds = computed(() => this.list().map((c) => c.id));
|
|
232
|
+
protected readonly allState = computed<'all' | 'some' | 'none'>(() => {
|
|
233
|
+
const ids = this.listIds();
|
|
234
|
+
const n = ids.filter((id) => this.selection().includes(id)).length;
|
|
235
|
+
return n === 0 ? 'none' : n === ids.length ? 'all' : 'some';
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
protected readonly classes = computed(() =>
|
|
239
|
+
cn('flex w-full flex-col bg-background text-foreground', this.userClass()),
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
protected isSelected(id: number): boolean {
|
|
243
|
+
return this.selection().includes(id);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
protected toggle(id: number): void {
|
|
247
|
+
this.selection.update((ids) => (ids.includes(id) ? ids.filter((x) => x !== id) : [...ids, id]));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
protected toggleAll(checked: boolean): void {
|
|
251
|
+
const ids = this.listIds();
|
|
252
|
+
this.selection.update((sel) =>
|
|
253
|
+
checked
|
|
254
|
+
? [...sel, ...ids.filter((id) => !sel.includes(id))]
|
|
255
|
+
: sel.filter((id) => !ids.includes(id as number)),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
protected pickStatus(status: Table01Status | null): void {
|
|
260
|
+
this.statusFilter.set(this.statusFilter() === status ? null : status);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
protected selectedText(count: number): string {
|
|
264
|
+
return count === 1 ? '1 selected' : `${count} selected`;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -20,6 +20,18 @@ import {
|
|
|
20
20
|
|
|
21
21
|
let nextId = 0;
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* La caja de la casilla (16px; marcada o mixta, `primary` con su sombra), según su
|
|
25
|
+
* `data-state="checked | indeterminate | unchecked"`. La usa también el Tree para dibujar sus
|
|
26
|
+
* casillas sin un control enfocable dentro del nodo (plan, G3.1-d).
|
|
27
|
+
*/
|
|
28
|
+
export const MIMI_CHECKBOX_BOX_STYLES = cn(
|
|
29
|
+
'inline-flex size-[var(--mimi-checkbox-size,1rem)] shrink-0 items-center justify-center rounded-[var(--mimi-checkbox-radius,var(--mimi-radius-sm))] border border-[color:var(--mimi-checkbox-border,var(--mimi-input))] bg-input-background text-[color:var(--mimi-checkbox-check,var(--mimi-primary-foreground))]',
|
|
30
|
+
'mimi-transition active:scale-(--mimi-press-scale-sm)',
|
|
31
|
+
'data-[state=checked]:border-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=checked]:bg-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=checked]:shadow-primary',
|
|
32
|
+
'data-[state=indeterminate]:border-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=indeterminate]:bg-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=indeterminate]:shadow-primary',
|
|
33
|
+
);
|
|
34
|
+
|
|
23
35
|
/**
|
|
24
36
|
* Casilla de verificación (docs/design/Mimi Componentes.dc.html, sección 7):
|
|
25
37
|
* `<mimi-checkbox [(checked)]="recordarme">Recordarme</mimi-checkbox>`.
|
|
@@ -148,12 +160,9 @@ export class MimiCheckbox implements FormCheckboxControl, MimiFieldControl {
|
|
|
148
160
|
cn('inline-flex items-center gap-2.5 text-sm', this.userClass()),
|
|
149
161
|
);
|
|
150
162
|
|
|
151
|
-
// Caja de 16px. Marcada o mixta: primary con su sombra. Foco: borde ring (sin marcar) y contorno.
|
|
152
163
|
protected readonly boxClasses = cn(
|
|
153
|
-
'peer
|
|
154
|
-
|
|
155
|
-
'data-[state=checked]:border-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=checked]:bg-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=checked]:shadow-primary',
|
|
156
|
-
'data-[state=indeterminate]:border-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=indeterminate]:bg-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=indeterminate]:shadow-primary',
|
|
164
|
+
'peer',
|
|
165
|
+
MIMI_CHECKBOX_BOX_STYLES,
|
|
157
166
|
'data-[state=unchecked]:focus-visible:border-ring data-[state=unchecked]:aria-invalid:border-destructive',
|
|
158
167
|
'disabled:shadow-none',
|
|
159
168
|
buttonFocusStyles,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { InjectionToken, type Provider } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Textos de Command, en archivo propio y sin @angular/aria ni el cdk: así se pueden proveer en
|
|
5
|
+
* `app.config.ts` sin llevar la paleta al chunk inicial.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Textos que la paleta muestra o anuncia. */
|
|
9
|
+
export interface MimiCommandText {
|
|
10
|
+
/** Texto del buscador vacío. */
|
|
11
|
+
placeholder: string;
|
|
12
|
+
/** Nombre del diálogo y de la lista, si no se da `aria-label`. */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Ayuda del pie: las flechas. */
|
|
15
|
+
navigate: string;
|
|
16
|
+
/** Ayuda del pie: Enter. */
|
|
17
|
+
run: string;
|
|
18
|
+
/** Número de resultados (pie y región `status`). */
|
|
19
|
+
results: (count: number) => string;
|
|
20
|
+
/** Título de «sin resultados». */
|
|
21
|
+
noResults: (query: string) => string;
|
|
22
|
+
/** Texto de «sin resultados». */
|
|
23
|
+
noResultsHint: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Textos predeterminados (inglés). */
|
|
27
|
+
export const MIMI_COMMAND_TEXT_EN: MimiCommandText = {
|
|
28
|
+
placeholder: 'Type a command or search…',
|
|
29
|
+
label: 'Command palette',
|
|
30
|
+
navigate: 'Navigate',
|
|
31
|
+
run: 'Open',
|
|
32
|
+
results: (count) => (count === 1 ? '1 result' : `${count} results`),
|
|
33
|
+
noResults: (query) => `No results for “${query}”`,
|
|
34
|
+
noResultsHint: 'Check the spelling or try another word.',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Textos en español: `provideMimiCommandText(MIMI_COMMAND_TEXT_ES)`. */
|
|
38
|
+
export const MIMI_COMMAND_TEXT_ES: MimiCommandText = {
|
|
39
|
+
placeholder: 'Escribe un comando o busca…',
|
|
40
|
+
label: 'Paleta de comandos',
|
|
41
|
+
navigate: 'Navegar',
|
|
42
|
+
run: 'Abrir',
|
|
43
|
+
results: (count) => (count === 1 ? '1 resultado' : `${count} resultados`),
|
|
44
|
+
noResults: (query) => `Sin resultados para «${query}»`,
|
|
45
|
+
noResultsHint: 'Revisa la ortografía o prueba con otra palabra.',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Textos activos. Sin `provideMimiCommandText`, los predeterminados en inglés. */
|
|
49
|
+
export const MIMI_COMMAND_TEXT = new InjectionToken<MimiCommandText>('MIMI_COMMAND_TEXT', {
|
|
50
|
+
providedIn: 'root',
|
|
51
|
+
factory: () => MIMI_COMMAND_TEXT_EN,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Cambia los textos de Command. Lo que no definas sigue en inglés:
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* provideMimiCommandText(MIMI_COMMAND_TEXT_ES)
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* Va en `app.config.ts` o en los `providers` de un componente o ruta.
|
|
62
|
+
*/
|
|
63
|
+
export function provideMimiCommandText(text: Partial<MimiCommandText>): Provider {
|
|
64
|
+
return { provide: MIMI_COMMAND_TEXT, useValue: { ...MIMI_COMMAND_TEXT_EN, ...text } };
|
|
65
|
+
}
|