@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
package/README.md
CHANGED
|
@@ -79,8 +79,10 @@ export class App {}
|
|
|
79
79
|
- **Checkbox**: `ng g mimi checkbox` · [documentación](https://ng.mimiworks.dev/docs/components/switch)
|
|
80
80
|
- **Color Picker**: `ng g mimi color-picker` · [documentación](https://ng.mimiworks.dev/docs/components/color-picker)
|
|
81
81
|
- **Combobox**: `ng g mimi combobox` · [documentación](https://ng.mimiworks.dev/docs/components/combobox)
|
|
82
|
+
- **Command**: `ng g mimi command` · [documentación](https://ng.mimiworks.dev/docs/components/command)
|
|
82
83
|
- **Confirm**: `ng g mimi confirm` · [documentación](https://ng.mimiworks.dev/docs/components/confirm)
|
|
83
84
|
- **Context Menu**: `ng g mimi context-menu` · [documentación](https://ng.mimiworks.dev/docs/components/context-menu)
|
|
85
|
+
- **Data Table**: `ng g mimi data-table` · [documentación](https://ng.mimiworks.dev/docs/components/data-table)
|
|
84
86
|
- **Date Picker**: `ng g mimi date-picker` · [documentación](https://ng.mimiworks.dev/docs/components/date-picker)
|
|
85
87
|
- **Date Range**: `ng g mimi date-range` · [documentación](https://ng.mimiworks.dev/docs/components/date-range)
|
|
86
88
|
- **Dialog**: `ng g mimi dialog` · [documentación](https://ng.mimiworks.dev/docs/components/dialog)
|
|
@@ -99,6 +101,7 @@ export class App {}
|
|
|
99
101
|
- **Separator**: `ng g mimi separator` · [documentación](https://ng.mimiworks.dev/docs/components/separator)
|
|
100
102
|
- **Skeleton**: `ng g mimi skeleton` · [documentación](https://ng.mimiworks.dev/docs/components/skeleton)
|
|
101
103
|
- **Slider**: `ng g mimi slider` · [documentación](https://ng.mimiworks.dev/docs/components/slider)
|
|
104
|
+
- **Sortable List**: `ng g mimi sortable-list` · [documentación](https://ng.mimiworks.dev/docs/components/sortable-list)
|
|
102
105
|
- **Switch**: `ng g mimi switch` · [documentación](https://ng.mimiworks.dev/docs/components/switch)
|
|
103
106
|
- **Tabs**: `ng g mimi tabs` · [documentación](https://ng.mimiworks.dev/docs/components/tabs)
|
|
104
107
|
- **Textarea**: `ng g mimi textarea` · [documentación](https://ng.mimiworks.dev/docs/components/input)
|
|
@@ -107,17 +110,24 @@ export class App {}
|
|
|
107
110
|
- **Toggle Group**: `ng g mimi toggle-group` · [documentación](https://ng.mimiworks.dev/docs/components/toggle-group)
|
|
108
111
|
- **Toolbar**: `ng g mimi toolbar` · [documentación](https://ng.mimiworks.dev/docs/components/toolbar)
|
|
109
112
|
- **Tooltip**: `ng g mimi tooltip` · [documentación](https://ng.mimiworks.dev/docs/components/tooltip)
|
|
113
|
+
- **Tree**: `ng g mimi tree` · [documentación](https://ng.mimiworks.dev/docs/components/tree)
|
|
110
114
|
|
|
111
115
|
## Blocks
|
|
112
116
|
|
|
113
|
-
Pantallas
|
|
117
|
+
Pantallas y secciones (inicio de sesión, registro, ajustes, precios…) hechas con los componentes de Mimi. Se agregan con el mismo comando, por ejemplo `ng g mimi` seguido del nombre del Block, y los componentes que usan van a la carpeta de siempre.
|
|
114
118
|
|
|
115
|
-
- **login-01
|
|
116
|
-
- **
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
- **
|
|
119
|
+
- **Las páginas** (`login-01`, `login-02`, `otp-01`, `settings-01`, `signup-01`, `table-01`) se generan como una página tuya: `ng g mimi login-01` crea `src/app/pages/login/` (`aliases.pages` en `mimi.json`) con `login.ts`, `login.html` y `login.routes.ts`, y la clase `LoginPage`. Al terminar te da la línea para `app.routes.ts` (la CLI no toca tus rutas). Otro nombre: `--name admin-login`. Si la carpeta ya existe, no la toca y te pide otro nombre.
|
|
120
|
+
- **Las secciones** (`pricing-01`) se copian a `src/app/blocks/<nombre>` (`aliases.blocks`): ponlas dentro de una de tus páginas.
|
|
121
|
+
|
|
122
|
+
El Block es tuyo; si lo modificas, `ng g mimi` no lo vuelve a tocar al actualizar (te avisa y `--overwrite` lo reemplaza entero). Mimi lo sigue por su nombre (`login-01`) aunque le pongas otro. Si instalaste una pantalla con la 0.4.0, sigue en `src/app/blocks/` y se actualiza ahí.
|
|
123
|
+
|
|
124
|
+
- **login-01** (página: inicio de sesión centrado, con Google y GitHub): `ng g mimi login-01` · [documentación](https://ng.mimiworks.dev/docs/blocks/login-01)
|
|
125
|
+
- **login-02** (página: inicio de sesión en dos columnas, con imagen y testimonio): `ng g mimi login-02` · [documentación](https://ng.mimiworks.dev/docs/blocks/login-02)
|
|
126
|
+
- **otp-01** (página: verificación con un código de 6 dígitos, reenvío y cuenta regresiva): `ng g mimi otp-01` · [documentación](https://ng.mimiworks.dev/docs/blocks/otp-01)
|
|
127
|
+
- **pricing-01** (sección: tres planes con mensual/anual y el plan destacado): `ng g mimi pricing-01` · [documentación](https://ng.mimiworks.dev/docs/blocks/pricing-01)
|
|
128
|
+
- **settings-01** (página: ajustes de cuenta con secciones, interruptores, sesiones y facturación): `ng g mimi settings-01` · [documentación](https://ng.mimiworks.dev/docs/blocks/settings-01)
|
|
129
|
+
- **table-01** (página: clientes con la Data Table en escritorio y una lista de tarjetas en móvil): `ng g mimi table-01` · [documentación](https://ng.mimiworks.dev/docs/blocks/table-01)
|
|
130
|
+
- **signup-01** (página: registro con validación, medidor de contraseña y confirmación): `ng g mimi signup-01` · [documentación](https://ng.mimiworks.dev/docs/blocks/signup-01)
|
|
121
131
|
|
|
122
132
|
## Tus cambios
|
|
123
133
|
|
|
@@ -130,6 +140,18 @@ Pantallas enteras (inicio de sesión, registro, ajustes…) hechas con los compo
|
|
|
130
140
|
|
|
131
141
|
Desde pnpm 10, los scripts de instalación de las dependencias están bloqueados hasta que los apruebes, y la instalación puede fallar con `ERR_PNPM_IGNORED_BUILDS`. Apruébalos con `pnpm approve-builds`. Más detalles en la [página de Instalación](https://ng.mimiworks.dev/docs/installation).
|
|
132
142
|
|
|
143
|
+
## Si los componentes salen sin estilos
|
|
144
|
+
|
|
145
|
+
Si `ng build` termina bien pero los componentes salen sin estilos, y el CSS compilado pesa mucho menos de lo normal (en una prueba, unos 33 kB en vez de unos 100 kB), puede que tu sistema bloquee el binario nativo de Tailwind. Lo vimos en Windows, con el Control de aplicaciones, que bloqueó el binario de Tailwind 4.3.5: Tailwind usa entonces un respaldo que no escanea tus archivos, y no da ningún error. No es de Mimi.
|
|
146
|
+
|
|
147
|
+
Para comprobarlo, busca una clase de tus componentes (por ejemplo `bg-background`) en el `styles-*.css` de `dist/<proyecto>/browser`: si no está, Tailwind no escaneó nada. Para ver si es el bloqueo, carga el binario desde la raíz del proyecto (Windows x64):
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
node -e "require('./node_modules/@tailwindcss/oxide-win32-x64-msvc/tailwindcss-oxide.win32-x64-msvc.node')"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Si falla con `ERR_DLOPEN_FAILED`, permite ese archivo en la política de tu sistema (con quien la administre) o fija una versión de Tailwind cuyo binario cargue; en la prueba donde lo vimos, la 4.3.3: `npm i -D --save-exact tailwindcss@4.3.3 @tailwindcss/postcss@4.3.3`. No desactives la política de seguridad para esto. Con otra versión, o en otro equipo, el resultado puede ser distinto.
|
|
154
|
+
|
|
133
155
|
## Licencia
|
|
134
156
|
|
|
135
157
|
MIT. Incluye íconos de Lucide (ISC); ver `THIRD_PARTY_NOTICES.md`.
|
package/dist/init/index.js
CHANGED
|
@@ -99,6 +99,7 @@ async function inspect(tree, options) {
|
|
|
99
99
|
sourceNot: (0, tailwind_1.tailwindSupportsSourceNot)(tree, deps['tailwindcss']),
|
|
100
100
|
componentsDir,
|
|
101
101
|
blocksDir: (0, paths_1.normalizePath)(mimiConfig?.aliases?.blocks ?? (0, project_1.defaultBlocksDir)(componentsDir)),
|
|
102
|
+
pagesDir: (0, paths_1.normalizePath)(mimiConfig?.aliases?.pages ?? (0, project_1.defaultPagesDir)(componentsDir)),
|
|
102
103
|
buildTsConfig: typeof tsConfig === 'string' ? (0, paths_1.normalizePath)(tsConfig) : undefined,
|
|
103
104
|
mimiConfig,
|
|
104
105
|
sourceRoot,
|
|
@@ -145,6 +146,11 @@ function updateGlobalCss(plan) {
|
|
|
145
146
|
if (plan.sourceNone && !(0, css_1.hasDirective)(css, '@source', blocksSourcePath)) {
|
|
146
147
|
lines.push(`@source "${blocksSourcePath}";`);
|
|
147
148
|
}
|
|
149
|
+
// B.11-i: y la de las páginas (los Blocks de tipo página).
|
|
150
|
+
const pagesSourcePath = (0, paths_1.relativePath)(cssDir, plan.pagesDir);
|
|
151
|
+
if (plan.sourceNone && !(0, css_1.hasDirective)(css, '@source', pagesSourcePath)) {
|
|
152
|
+
lines.push(`@source "${pagesSourcePath}";`);
|
|
153
|
+
}
|
|
148
154
|
// Sin source(none), Tailwind escanea todo el workspace salvo lo que ignora git, y .mimi/ va
|
|
149
155
|
// en git: las clases de las copias originales acabarían en el CSS aunque el usuario ya las
|
|
150
156
|
// haya cambiado o quitado en sus componentes.
|
|
@@ -249,6 +255,8 @@ function writeMimiConfig(plan) {
|
|
|
249
255
|
theme: `${plan.sourceRoot}/app/mimi.preset.ts`,
|
|
250
256
|
// Desde la 0.4.0 (B.1-a). Los proyectos anteriores lo reciben con el primer Block.
|
|
251
257
|
blocks: plan.blocksDir,
|
|
258
|
+
// Desde la B.11. Los proyectos anteriores lo reciben con la primera página.
|
|
259
|
+
pages: plan.pagesDir,
|
|
252
260
|
},
|
|
253
261
|
components: {},
|
|
254
262
|
};
|
package/dist/registry.js
CHANGED
|
@@ -3,10 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.registry = void 0;
|
|
6
|
+
exports.isPageBlock = exports.registry = void 0;
|
|
7
7
|
exports.resolveItems = resolveItems;
|
|
8
8
|
const registry_json_1 = __importDefault(require("./registry.json"));
|
|
9
9
|
exports.registry = registry_json_1.default;
|
|
10
|
+
/** ¿El ítem es un Block que se genera como página (B.11)? */
|
|
11
|
+
const isPageBlock = (name, reg = exports.registry) => reg.items[name]?.type === 'block' && reg.items[name].kind === 'page';
|
|
12
|
+
exports.isPageBlock = isPageBlock;
|
|
10
13
|
/**
|
|
11
14
|
* El ítem y todas sus `registryDependencies`, sin repetir, dependencias primero.
|
|
12
15
|
* Lanza un error si el nombre no existe.
|
package/dist/registry.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.5.0",
|
|
3
3
|
"suggestedDependencies": {
|
|
4
4
|
"@lucide/angular": "^1.47.0"
|
|
5
5
|
},
|
|
@@ -265,6 +265,23 @@
|
|
|
265
265
|
"utils/cn"
|
|
266
266
|
]
|
|
267
267
|
},
|
|
268
|
+
"data-table": {
|
|
269
|
+
"type": "component",
|
|
270
|
+
"files": [
|
|
271
|
+
"components/data-table/data-table.ts",
|
|
272
|
+
"components/data-table/data-table-texts.ts",
|
|
273
|
+
"components/data-table/index.ts"
|
|
274
|
+
],
|
|
275
|
+
"registryDependencies": [
|
|
276
|
+
"checkbox",
|
|
277
|
+
"chip",
|
|
278
|
+
"dropdown-menu",
|
|
279
|
+
"input",
|
|
280
|
+
"pagination",
|
|
281
|
+
"toggle-group",
|
|
282
|
+
"utils/cn"
|
|
283
|
+
]
|
|
284
|
+
},
|
|
268
285
|
"input-mask": {
|
|
269
286
|
"type": "component",
|
|
270
287
|
"files": [
|
|
@@ -334,6 +351,20 @@
|
|
|
334
351
|
"utils/cn"
|
|
335
352
|
]
|
|
336
353
|
},
|
|
354
|
+
"sortable-list": {
|
|
355
|
+
"type": "component",
|
|
356
|
+
"files": [
|
|
357
|
+
"components/sortable-list/sortable-list.ts",
|
|
358
|
+
"components/sortable-list/sortable-list-texts.ts",
|
|
359
|
+
"components/sortable-list/index.ts"
|
|
360
|
+
],
|
|
361
|
+
"peerDependencies": {
|
|
362
|
+
"@angular/cdk": "^22.2.4"
|
|
363
|
+
},
|
|
364
|
+
"registryDependencies": [
|
|
365
|
+
"utils/cn"
|
|
366
|
+
]
|
|
367
|
+
},
|
|
337
368
|
"slider": {
|
|
338
369
|
"type": "component",
|
|
339
370
|
"files": [
|
|
@@ -374,6 +405,22 @@
|
|
|
374
405
|
"utils/overlay"
|
|
375
406
|
]
|
|
376
407
|
},
|
|
408
|
+
"command": {
|
|
409
|
+
"type": "component",
|
|
410
|
+
"files": [
|
|
411
|
+
"components/command/command.ts",
|
|
412
|
+
"components/command/command-texts.ts",
|
|
413
|
+
"components/command/index.ts"
|
|
414
|
+
],
|
|
415
|
+
"peerDependencies": {
|
|
416
|
+
"@angular/aria": "^22.2.4"
|
|
417
|
+
},
|
|
418
|
+
"registryDependencies": [
|
|
419
|
+
"dialog",
|
|
420
|
+
"option-list",
|
|
421
|
+
"utils/cn"
|
|
422
|
+
]
|
|
423
|
+
},
|
|
377
424
|
"calendar": {
|
|
378
425
|
"type": "component",
|
|
379
426
|
"files": [
|
|
@@ -655,6 +702,22 @@
|
|
|
655
702
|
"utils/overlay"
|
|
656
703
|
]
|
|
657
704
|
},
|
|
705
|
+
"tree": {
|
|
706
|
+
"type": "component",
|
|
707
|
+
"files": [
|
|
708
|
+
"components/tree/tree.ts",
|
|
709
|
+
"components/tree/tree-texts.ts",
|
|
710
|
+
"components/tree/index.ts"
|
|
711
|
+
],
|
|
712
|
+
"peerDependencies": {
|
|
713
|
+
"@angular/cdk": "^22.2.4",
|
|
714
|
+
"@angular/forms": "^22.0.0"
|
|
715
|
+
},
|
|
716
|
+
"registryDependencies": [
|
|
717
|
+
"checkbox",
|
|
718
|
+
"utils/cn"
|
|
719
|
+
]
|
|
720
|
+
},
|
|
658
721
|
"textarea": {
|
|
659
722
|
"type": "component",
|
|
660
723
|
"files": [
|
|
@@ -673,6 +736,7 @@
|
|
|
673
736
|
},
|
|
674
737
|
"login-01": {
|
|
675
738
|
"type": "block",
|
|
739
|
+
"kind": "page",
|
|
676
740
|
"files": [
|
|
677
741
|
"blocks/login-01/login-01.ts",
|
|
678
742
|
"blocks/login-01/login-01.html",
|
|
@@ -694,6 +758,7 @@
|
|
|
694
758
|
},
|
|
695
759
|
"login-02": {
|
|
696
760
|
"type": "block",
|
|
761
|
+
"kind": "page",
|
|
697
762
|
"files": [
|
|
698
763
|
"blocks/login-02/login-02.ts",
|
|
699
764
|
"blocks/login-02/login-02.html",
|
|
@@ -712,6 +777,7 @@
|
|
|
712
777
|
},
|
|
713
778
|
"signup-01": {
|
|
714
779
|
"type": "block",
|
|
780
|
+
"kind": "page",
|
|
715
781
|
"files": [
|
|
716
782
|
"blocks/signup-01/signup-01.ts",
|
|
717
783
|
"blocks/signup-01/signup-01.html",
|
|
@@ -731,6 +797,7 @@
|
|
|
731
797
|
},
|
|
732
798
|
"otp-01": {
|
|
733
799
|
"type": "block",
|
|
800
|
+
"kind": "page",
|
|
734
801
|
"files": [
|
|
735
802
|
"blocks/otp-01/otp-01.ts",
|
|
736
803
|
"blocks/otp-01/otp-01.html",
|
|
@@ -745,6 +812,7 @@
|
|
|
745
812
|
},
|
|
746
813
|
"settings-01": {
|
|
747
814
|
"type": "block",
|
|
815
|
+
"kind": "page",
|
|
748
816
|
"files": [
|
|
749
817
|
"blocks/settings-01/settings-01.ts",
|
|
750
818
|
"blocks/settings-01/settings-01.html",
|
|
@@ -768,8 +836,26 @@
|
|
|
768
836
|
"utils/cn"
|
|
769
837
|
]
|
|
770
838
|
},
|
|
839
|
+
"table-01": {
|
|
840
|
+
"type": "block",
|
|
841
|
+
"kind": "page",
|
|
842
|
+
"files": [
|
|
843
|
+
"blocks/table-01/table-01.ts",
|
|
844
|
+
"blocks/table-01/table-01.html",
|
|
845
|
+
"blocks/table-01/index.ts"
|
|
846
|
+
],
|
|
847
|
+
"registryDependencies": [
|
|
848
|
+
"badge",
|
|
849
|
+
"button",
|
|
850
|
+
"checkbox",
|
|
851
|
+
"data-table",
|
|
852
|
+
"input",
|
|
853
|
+
"utils/cn"
|
|
854
|
+
]
|
|
855
|
+
},
|
|
771
856
|
"pricing-01": {
|
|
772
857
|
"type": "block",
|
|
858
|
+
"kind": "section",
|
|
773
859
|
"files": [
|
|
774
860
|
"blocks/pricing-01/pricing-01.ts",
|
|
775
861
|
"blocks/pricing-01/pricing-01.html",
|
|
@@ -33,9 +33,18 @@ let nextId = 0;
|
|
|
33
33
|
* login-01 (Block de Mimi): inicio de sesión centrado con correo, contraseña, «recordarme» y
|
|
34
34
|
* botones de Google y GitHub. Diseño: docs/design/Block login-01.dc.html.
|
|
35
35
|
*
|
|
36
|
-
* Es una
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* Es una página: `ng g mimi login-01` la genera en tu carpeta de páginas (`aliases.pages` de
|
|
37
|
+
* mimi.json; por defecto, `src/app/pages/login/`, u otro nombre con `--name`) junto con su
|
|
38
|
+
* archivo de rutas (`login.routes.ts`, con la página en carga diferida), y te da la línea para
|
|
39
|
+
* `app.routes.ts`:
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* { path: 'login', loadChildren: () => import('./pages/login/login.routes').then((m) => m.LOGIN_ROUTES) },
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* El archivo es tuyo: conecta tu autenticación aquí mismo (en `onSubmit`, inyectando tu
|
|
46
|
+
* servicio), cambia los textos, quita las redes o agrega campos. Si la usas dentro de otra
|
|
47
|
+
* página, sus entradas y salidas siguen ahí:
|
|
39
48
|
*
|
|
40
49
|
* ```html
|
|
41
50
|
* <mimi-login-01 [loading]="auth.loading()" [error]="auth.error()" (submitted)="auth.signIn($event)" />
|
|
@@ -26,9 +26,18 @@ let nextId = 0;
|
|
|
26
26
|
* login-02 (Block de Mimi): inicio de sesión dividido en dos, con el formulario a un lado y una
|
|
27
27
|
* imagen con un testimonio en vidrio al otro. Diseño: docs/design/Block login-02.dc.html.
|
|
28
28
|
*
|
|
29
|
-
* Es una
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Es una página: `ng g mimi login-02` la genera en tu carpeta de páginas (`aliases.pages` de
|
|
30
|
+
* mimi.json; por defecto, `src/app/pages/login/`, u otro nombre con `--name` si ya tienes la de
|
|
31
|
+
* login-01) junto con su archivo de rutas (`login.routes.ts`, con la página en carga diferida),
|
|
32
|
+
* y te da la línea para `app.routes.ts`:
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* { path: 'login', loadChildren: () => import('./pages/login/login.routes').then((m) => m.LOGIN_ROUTES) },
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Da tu imagen con `imageSrc` (y `imageAlt` si no es decorativa). El testimonio está escrito en
|
|
39
|
+
* la plantilla: cámbialo ahí. Si la usas dentro de otra página, sus entradas y salidas siguen
|
|
40
|
+
* ahí:
|
|
32
41
|
*
|
|
33
42
|
* ```html
|
|
34
43
|
* <mimi-login-02 imageSrc="/img/login.jpg" [loading]="auth.loading()" (submitted)="auth.signIn($event)" />
|
|
@@ -29,9 +29,18 @@ let nextId = 0;
|
|
|
29
29
|
* otp-01 (Block de Mimi): verificación con un código de 6 dígitos, con reenvío y cuenta
|
|
30
30
|
* regresiva. Diseño: docs/design/Block otp-01.dc.html.
|
|
31
31
|
*
|
|
32
|
-
* Es una
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* Es una página, que va después del registro o del inicio de sesión: `ng g mimi otp-01` la
|
|
33
|
+
* genera en tu carpeta de páginas (`aliases.pages` de mimi.json; por defecto, `src/app/pages/otp/`,
|
|
34
|
+
* u otro nombre con `--name`) junto con su archivo de rutas (`otp.routes.ts`, con la página en
|
|
35
|
+
* carga diferida), y te da la línea para `app.routes.ts`:
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* { path: 'otp', loadChildren: () => import('./pages/otp/otp.routes').then((m) => m.OTP_ROUTES) },
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* `verify` sale con el código (al completar los dígitos o con el botón, una sola vez por
|
|
42
|
+
* código); tu app lo comprueba y responde con `status`: conéctalo aquí mismo o, si la usas dentro
|
|
43
|
+
* de otra página, por sus entradas y salidas:
|
|
35
44
|
*
|
|
36
45
|
* ```html
|
|
37
46
|
* <mimi-otp-01 destination="a••••@example.com" [status]="otp.status()" [loading]="otp.checking()"
|
|
@@ -167,7 +167,7 @@ export class Pricing01 {
|
|
|
167
167
|
|
|
168
168
|
protected readonly classes = computed(() =>
|
|
169
169
|
cn(
|
|
170
|
-
'flex
|
|
170
|
+
'flex w-full flex-col items-center gap-7 bg-background px-[18px] pt-9 pb-8 text-foreground md:px-10 md:py-14',
|
|
171
171
|
this.userClass(),
|
|
172
172
|
),
|
|
173
173
|
);
|
|
@@ -146,9 +146,19 @@ let nextId = 0;
|
|
|
146
146
|
* de peligro, facturación y una barra de «cambios sin guardar».
|
|
147
147
|
* Diseño: docs/design/Block settings-01.dc.html.
|
|
148
148
|
*
|
|
149
|
-
* Es una
|
|
150
|
-
*
|
|
151
|
-
*
|
|
149
|
+
* Es una página: `ng g mimi settings-01` la genera en tu carpeta de páginas (`aliases.pages` de
|
|
150
|
+
* mimi.json; por defecto, `src/app/pages/settings/`, u otro nombre con `--name`) junto con su
|
|
151
|
+
* archivo de rutas (`settings.routes.ts`, con la página en carga diferida), y te da la línea
|
|
152
|
+
* para `app.routes.ts`:
|
|
153
|
+
*
|
|
154
|
+
* ```ts
|
|
155
|
+
* { path: 'settings', loadChildren: () => import('./pages/settings/settings.routes').then((m) => m.SETTINGS_ROUTES) },
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* Dale tus datos (`profile`, `preferences`, `sessions`, `plan`) aquí mismo o, si la usas dentro
|
|
159
|
+
* de otra página, por sus entradas. Sin datos, muestra los del ejemplo. «Guardar» emite `saved`
|
|
160
|
+
* con el formulario; las acciones (foto, sesiones, borrar la cuenta, cambiar de plan) son
|
|
161
|
+
* salidas: tu app decide.
|
|
152
162
|
*
|
|
153
163
|
* ```html
|
|
154
164
|
* <mimi-settings-01 [profile]="me()" [saving]="saving()" (saved)="save($event)" (deleteAccount)="confirmDelete()" />
|
|
@@ -40,8 +40,18 @@ let nextId = 0;
|
|
|
40
40
|
* contraseña con reglas, confirmación y un botón que dice qué falta.
|
|
41
41
|
* Diseño: docs/design/Block signup-01.dc.html.
|
|
42
42
|
*
|
|
43
|
-
* Es una
|
|
44
|
-
*
|
|
43
|
+
* Es una página: `ng g mimi signup-01` la genera en tu carpeta de páginas (`aliases.pages` de
|
|
44
|
+
* mimi.json; por defecto, `src/app/pages/signup/`, u otro nombre con `--name`) junto con su
|
|
45
|
+
* archivo de rutas (`signup.routes.ts`, con la página en carga diferida), y te da la línea para
|
|
46
|
+
* `app.routes.ts`:
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* { path: 'signup', loadChildren: () => import('./pages/signup/signup.routes').then((m) => m.SIGNUP_ROUTES) },
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* El texto de los beneficios y los mensajes están en la plantilla y en este archivo: cámbialos
|
|
53
|
+
* aquí, y conecta tu registro en `onSubmit`. Si la usas dentro de otra página, sus entradas y
|
|
54
|
+
* salidas siguen ahí:
|
|
45
55
|
*
|
|
46
56
|
* ```html
|
|
47
57
|
* <mimi-signup-01 [loading]="auth.loading()" [error]="auth.error()" (submitted)="auth.signUp($event)" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './table-01';
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
<header class="flex items-center justify-between gap-3 px-4 pt-5 pb-3.5 md:px-8 md:pt-7 md:pb-5">
|
|
2
|
+
<div class="min-w-0">
|
|
3
|
+
<h1 class="text-2xl font-extrabold tracking-[-0.03em] md:text-[28px]">Customers</h1>
|
|
4
|
+
<p class="text-sm text-muted-foreground tabular-nums">{{ subtitle() }}</p>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
7
|
+
<button
|
|
8
|
+
type="button"
|
|
9
|
+
mimiBtn
|
|
10
|
+
variant="outline"
|
|
11
|
+
class="max-md:hidden"
|
|
12
|
+
(click)="importCsv.emit()"
|
|
13
|
+
>
|
|
14
|
+
<svg
|
|
15
|
+
aria-hidden="true"
|
|
16
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
17
|
+
width="16"
|
|
18
|
+
height="16"
|
|
19
|
+
viewBox="0 0 24 24"
|
|
20
|
+
fill="none"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
stroke-width="2"
|
|
23
|
+
stroke-linecap="round"
|
|
24
|
+
stroke-linejoin="round"
|
|
25
|
+
>
|
|
26
|
+
<!-- Lucide upload (ISC) -->
|
|
27
|
+
<path d="M12 3v12" />
|
|
28
|
+
<path d="m17 8-5-5-5 5" />
|
|
29
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
30
|
+
</svg>
|
|
31
|
+
Import CSV
|
|
32
|
+
</button>
|
|
33
|
+
<button type="button" mimiBtn aria-label="New customer" (click)="newCustomer.emit()">
|
|
34
|
+
<svg
|
|
35
|
+
aria-hidden="true"
|
|
36
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
37
|
+
width="16"
|
|
38
|
+
height="16"
|
|
39
|
+
viewBox="0 0 24 24"
|
|
40
|
+
fill="none"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
stroke-width="2"
|
|
43
|
+
stroke-linecap="round"
|
|
44
|
+
stroke-linejoin="round"
|
|
45
|
+
>
|
|
46
|
+
<!-- Lucide plus (ISC) -->
|
|
47
|
+
<path d="M5 12h14" />
|
|
48
|
+
<path d="M12 5v14" />
|
|
49
|
+
</svg>
|
|
50
|
+
<span class="max-md:hidden">New customer</span>
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
</header>
|
|
54
|
+
|
|
55
|
+
<!-- Escritorio: el resumen y la Data Table. -->
|
|
56
|
+
<div class="flex flex-col gap-4 px-8 pb-8 max-md:hidden">
|
|
57
|
+
<ul class="flex flex-wrap gap-x-7 gap-y-2" aria-label="Customers by status">
|
|
58
|
+
@for (item of counts(); track item.id) {
|
|
59
|
+
<li class="flex items-center gap-2 text-sm">
|
|
60
|
+
<span aria-hidden="true" [class]="'size-2 rounded-full ' + dotClass(item.tone)"></span>
|
|
61
|
+
<span class="text-muted-foreground">{{ item.plural }}</span>
|
|
62
|
+
<span class="font-bold tabular-nums">{{ item.count }}</span>
|
|
63
|
+
</li>
|
|
64
|
+
}
|
|
65
|
+
</ul>
|
|
66
|
+
<mimi-data-table
|
|
67
|
+
aria-label="Customers"
|
|
68
|
+
searchPlaceholder="Search customer, email or ID…"
|
|
69
|
+
selectable
|
|
70
|
+
[data]="customers()"
|
|
71
|
+
[columns]="columns()"
|
|
72
|
+
[(selection)]="selection"
|
|
73
|
+
>
|
|
74
|
+
<ng-template mimiTableCell="name" let-row>
|
|
75
|
+
<span class="flex min-w-0 flex-col leading-[1.3]">
|
|
76
|
+
<span class="truncate font-semibold">{{ row.name }}</span>
|
|
77
|
+
<span class="truncate text-[12.5px] text-muted-foreground">{{ row.email }}</span>
|
|
78
|
+
</span>
|
|
79
|
+
</ng-template>
|
|
80
|
+
<ng-template mimiTableCell="status" let-row>
|
|
81
|
+
<span mimiBadge variant="soft" [tone]="statusOf(row.status).tone">{{
|
|
82
|
+
statusOf(row.status).label
|
|
83
|
+
}}</span>
|
|
84
|
+
</ng-template>
|
|
85
|
+
<ng-template mimiTableCell="mrr" let-row>
|
|
86
|
+
<span class="font-semibold">{{ format(row.mrr) }}</span>
|
|
87
|
+
</ng-template>
|
|
88
|
+
<ng-template mimiTableBulk let-ids>
|
|
89
|
+
<button type="button" mimiBtn variant="outline" size="sm" (click)="exported.emit(ids)">
|
|
90
|
+
Export
|
|
91
|
+
</button>
|
|
92
|
+
<button type="button" mimiBtn tone="danger" size="sm" (click)="deleted.emit(ids)">
|
|
93
|
+
Delete
|
|
94
|
+
</button>
|
|
95
|
+
</ng-template>
|
|
96
|
+
</mimi-data-table>
|
|
97
|
+
<p class="text-[12.5px] text-muted-foreground">
|
|
98
|
+
Select rows to see the bulk actions (Export, Delete). Filter by Status or Plan from the header.
|
|
99
|
+
</p>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Móvil: búsqueda, píldoras de estado y la lista de tarjetas. -->
|
|
103
|
+
<div class="flex flex-col gap-3 px-4 pb-24 md:hidden">
|
|
104
|
+
<input
|
|
105
|
+
mimiInput
|
|
106
|
+
type="search"
|
|
107
|
+
aria-label="Search customers"
|
|
108
|
+
placeholder="Search customer…"
|
|
109
|
+
[value]="query()"
|
|
110
|
+
(input)="query.set($any($event.target).value)"
|
|
111
|
+
/>
|
|
112
|
+
<div role="group" aria-label="Filter by status" class="-mx-4 flex gap-2 overflow-x-auto px-4">
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
class="inline-flex h-8 shrink-0 cursor-pointer items-center gap-1.5 rounded-full border border-border bg-card px-3 text-[13px] font-medium mimi-transition focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring aria-pressed:border-primary aria-pressed:bg-primary aria-pressed:text-primary-foreground"
|
|
116
|
+
[attr.aria-pressed]="statusFilter() === null"
|
|
117
|
+
(click)="statusFilter.set(null)"
|
|
118
|
+
>
|
|
119
|
+
All <span class="tabular-nums opacity-70">{{ customers().length }}</span>
|
|
120
|
+
</button>
|
|
121
|
+
@for (item of counts(); track item.id) {
|
|
122
|
+
<button
|
|
123
|
+
type="button"
|
|
124
|
+
class="inline-flex h-8 shrink-0 cursor-pointer items-center gap-1.5 rounded-full border border-border bg-card px-3 text-[13px] font-medium mimi-transition focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring aria-pressed:border-primary aria-pressed:bg-primary aria-pressed:text-primary-foreground"
|
|
125
|
+
[attr.aria-pressed]="statusFilter() === item.id"
|
|
126
|
+
(click)="pickStatus(item.id)"
|
|
127
|
+
>
|
|
128
|
+
{{ item.plural }} <span class="tabular-nums opacity-70">{{ item.count }}</span>
|
|
129
|
+
</button>
|
|
130
|
+
}
|
|
131
|
+
</div>
|
|
132
|
+
<div class="flex items-center justify-between text-[13px] text-muted-foreground">
|
|
133
|
+
<label class="flex items-center gap-2.5">
|
|
134
|
+
<mimi-checkbox
|
|
135
|
+
aria-label="Select all"
|
|
136
|
+
[checked]="allState() === 'all'"
|
|
137
|
+
[indeterminate]="allState() === 'some'"
|
|
138
|
+
(checkedChange)="toggleAll($event)"
|
|
139
|
+
/>
|
|
140
|
+
Select all
|
|
141
|
+
</label>
|
|
142
|
+
<span class="tabular-nums" role="status">{{ list().length }} results</span>
|
|
143
|
+
</div>
|
|
144
|
+
<ul class="flex flex-col gap-2">
|
|
145
|
+
@for (customer of list(); track customer.id) {
|
|
146
|
+
<li
|
|
147
|
+
class="flex items-center gap-3 rounded-[var(--mimi-radius-card)] border bg-card p-3 data-[selected]:border-info/45 data-[selected]:bg-[color-mix(in_oklab,var(--mimi-info)_8%,var(--mimi-card))]"
|
|
148
|
+
[attr.data-selected]="isSelected(customer.id) ? '' : null"
|
|
149
|
+
>
|
|
150
|
+
<mimi-checkbox
|
|
151
|
+
[aria-label]="'Select ' + customer.name"
|
|
152
|
+
[checked]="isSelected(customer.id)"
|
|
153
|
+
(checkedChange)="toggle(customer.id)"
|
|
154
|
+
/>
|
|
155
|
+
<span class="flex min-w-0 flex-1 flex-col leading-[1.3]">
|
|
156
|
+
<span class="truncate text-sm font-semibold">{{ customer.name }}</span>
|
|
157
|
+
<span class="truncate text-[12.5px] text-muted-foreground tabular-nums"
|
|
158
|
+
>{{ customer.plan }} · {{ format(customer.mrr) }}</span
|
|
159
|
+
>
|
|
160
|
+
</span>
|
|
161
|
+
<span mimiBadge variant="soft" [tone]="statusOf(customer.status).tone">{{
|
|
162
|
+
statusOf(customer.status).label
|
|
163
|
+
}}</span>
|
|
164
|
+
</li>
|
|
165
|
+
} @empty {
|
|
166
|
+
<li
|
|
167
|
+
class="rounded-[var(--mimi-radius-card)] border border-dashed p-6 text-center text-sm text-muted-foreground"
|
|
168
|
+
>
|
|
169
|
+
No customers match.
|
|
170
|
+
</li>
|
|
171
|
+
}
|
|
172
|
+
</ul>
|
|
173
|
+
@if (selection().length) {
|
|
174
|
+
<div
|
|
175
|
+
role="toolbar"
|
|
176
|
+
aria-label="Bulk actions"
|
|
177
|
+
class="fixed inset-x-3 bottom-3 z-10 flex items-center gap-2 rounded-[var(--mimi-radius-card)] bg-foreground py-2 pr-2 pl-4 text-background shadow-[shadow:var(--mimi-shadow-popover)]"
|
|
178
|
+
>
|
|
179
|
+
<span class="flex-1 text-sm font-semibold tabular-nums" role="status">{{
|
|
180
|
+
selectedText(selection().length)
|
|
181
|
+
}}</span>
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
mimiBtn
|
|
185
|
+
variant="ghost"
|
|
186
|
+
size="sm"
|
|
187
|
+
class="text-background hover:bg-background/15 hover:text-background"
|
|
188
|
+
(click)="exported.emit(selection())"
|
|
189
|
+
>
|
|
190
|
+
Export
|
|
191
|
+
</button>
|
|
192
|
+
<button
|
|
193
|
+
type="button"
|
|
194
|
+
mimiBtn
|
|
195
|
+
variant="ghost"
|
|
196
|
+
size="sm"
|
|
197
|
+
class="text-background hover:bg-background/15 hover:text-background"
|
|
198
|
+
(click)="deleted.emit(selection())"
|
|
199
|
+
>
|
|
200
|
+
Delete
|
|
201
|
+
</button>
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
mimiBtn
|
|
205
|
+
variant="ghost"
|
|
206
|
+
size="sm"
|
|
207
|
+
class="text-background hover:bg-background/15 hover:text-background"
|
|
208
|
+
(click)="selection.set([])"
|
|
209
|
+
>
|
|
210
|
+
Clear
|
|
211
|
+
</button>
|
|
212
|
+
</div>
|
|
213
|
+
}
|
|
214
|
+
</div>
|