lkd-web-kit 0.9.2 → 0.10.1

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.
@@ -0,0 +1,168 @@
1
+ ---
2
+ name: create-modal-component
3
+ description: Crear modales en proyectos internos con el modal manager global de lkd-web-kit. Usar cuando Codex deba agregar, modificar o registrar modales bajo src/components/modals, envolverlos con withModalManager, elegir entre un modal interno del proyecto o Modal de Mantine, organizar el modal en carpeta propia, tipar props para showModal, actualizar dynamicModals, MyModalManagerProvider, module augmentation o loadModals.
4
+ ---
5
+
6
+ # Crear Modal Component
7
+
8
+ Usar este flujo para modales abiertos con `useModalManager().showModal(...)`.
9
+
10
+ ## Lectura Obligatoria
11
+
12
+ 1. Leer `docs/lkd-web-kit.md` y `docs/local-components.md`.
13
+ 2. Leer `src/components/modals/index.ts`.
14
+ 3. Leer `src/contexts/MyModalManagerContext/index.tsx`.
15
+ 4. Leer `src/types/lkd-web-kit.d.ts`.
16
+ 5. Buscar si existe un modal interno en `src/components/ui` o componentes compartidos antes de importar `Modal` de Mantine.
17
+ 6. Leer un modal existente del mismo dominio en `src/components/modals/{dominio}`.
18
+ 7. Si se toca `next/dynamic` o lazy loading, leer `node_modules/next/dist/docs/01-app/02-guides/lazy-loading.md`.
19
+
20
+ ## Arquitectura Actual
21
+
22
+ - `lkd-web-kit` exporta `ModalManagerProvider`, `useModalManager`, `withModalManager`, `ModalRegistryItem`, `ModalKey` y tipos relacionados.
23
+ - El proyecto no usa `src/contexts/ModalManagerContext`; no recrearlo.
24
+ - `src/contexts/MyModalManagerContext/index.tsx` adapta el provider del kit y le pasa `dynamicModals`.
25
+ - `src/types/lkd-web-kit.d.ts` hace module augmentation para que `showModal` infiera keys y props desde `dynamicModals`.
26
+ - `src/app/PageProviders.tsx` envuelve la app con `MyModalManagerProvider` y propaga `loadModals`.
27
+
28
+ ## Estructura
29
+
30
+ - Crear modales en `src/components/modals/{dominio}/{NombreModal}/index.tsx` cuando tengan formulario, schema, hooks internos, assets o subcomponentes.
31
+ - Usar archivo plano `src/components/modals/{dominio}/{NombreModal}.tsx` solo para modales triviales de una pieza, siguiendo el patron existente.
32
+ - Colocar piezas privadas junto al modal: `schema.ts`, `helpers.ts`, subcomponentes o assets dentro de la misma carpeta.
33
+ - No mover logica compartida a `src/components/ui` salvo que ya exista mas de un consumidor real.
34
+
35
+ ## Implementacion
36
+
37
+ 1. Definir props exportadas si el modal recibe datos desde `showModal`:
38
+
39
+ ```tsx
40
+ export interface MyModalProps {
41
+ itemId: string;
42
+ onSuccess?: () => void;
43
+ }
44
+ ```
45
+
46
+ 2. Envolver el componente con `withModalManager<Props>`.
47
+ 3. Recibir `modalProps` desde el wrapper y pasarlo al modal raiz.
48
+ 4. Antes de usar `Modal` de `@mantine/core`, corroborar si el proyecto tiene un modal interno que ya envuelve Mantine y usarlo si cubre el caso.
49
+ 5. Usar clases Tailwind para spacing, radius, shadow, margin y padding; evitar props Mantine equivalentes si `className` resuelve el caso.
50
+ 6. Usar wrappers locales del proyecto o `lkd-web-kit` antes que primitivas Mantine directas.
51
+ 7. No usar `any`.
52
+
53
+ Modal base con modal interno:
54
+
55
+ ```tsx
56
+ import { withModalManager } from "lkd-web-kit";
57
+ import ButtonAE from "src/components/ui/ButtonAE";
58
+ import InternalModal from "src/components/ui/InternalModal";
59
+
60
+ export interface MyModalProps {
61
+ onConfirm?: () => void;
62
+ }
63
+
64
+ const MyModal = withModalManager<MyModalProps>(
65
+ ({ onConfirm, modalProps }) => {
66
+ return (
67
+ <InternalModal {...modalProps} size="lg">
68
+ <InternalModal.Header>Titulo</InternalModal.Header>
69
+ <InternalModal.Body className="grid gap-4">Contenido</InternalModal.Body>
70
+ <InternalModal.Footer>
71
+ <ButtonAE
72
+ onClick={() => {
73
+ onConfirm?.();
74
+ modalProps.onClose?.();
75
+ }}
76
+ >
77
+ Guardar
78
+ </ButtonAE>
79
+ </InternalModal.Footer>
80
+ </InternalModal>
81
+ );
82
+ },
83
+ );
84
+
85
+ export default MyModal;
86
+ ```
87
+
88
+ Fallback con Mantine, solo si no hay modal interno aplicable:
89
+
90
+ ```tsx
91
+ import { Modal } from "@mantine/core";
92
+ import { withModalManager } from "lkd-web-kit";
93
+
94
+ const MyModal = withModalManager(({ modalProps }) => (
95
+ <Modal {...modalProps} title="Titulo">
96
+ Contenido
97
+ </Modal>
98
+ ));
99
+
100
+ export default MyModal;
101
+ ```
102
+
103
+ ## Registro
104
+
105
+ 1. Exportar las props publicas desde el modal.
106
+ 2. Importar el tipo de props en `src/components/modals/index.ts`:
107
+
108
+ ```ts
109
+ import type { MyModalProps } from "./domain/MyModal";
110
+ ```
111
+
112
+ 3. Registrar con `dynamicModal<Props>("domain/MyModal")`:
113
+
114
+ ```ts
115
+ "domain.myModal": dynamicModal<MyModalProps>("domain/MyModal"),
116
+ ```
117
+
118
+ 4. Usar una key estable con formato `{dominio}.{accion}`.
119
+ 5. No tocar `src/types/lkd-web-kit.d.ts` si `dynamicModals` sigue siendo la fuente unica. La augmentacion ya apunta a `typeof dynamicModals`.
120
+ 6. Si se cambia la forma del registro, mantener `ModalRegistryItem<T>` y que cada item tenga `component`, `load` y `path`.
121
+
122
+ ## Contexto y Precarga
123
+
124
+ - Para rutas que ya usan `PageProviders`, pasar `loadModals` ahi:
125
+
126
+ ```tsx
127
+ <PageProviders loadModals={["domain.myModal"]}>{children}</PageProviders>
128
+ ```
129
+
130
+ - `loadModals` acepta una key exacta o un prefijo, por ejemplo `"development"` para precargar todos los modales cuyo key empieza con ese prefijo.
131
+ - Para layouts que no usan `PageProviders`, envolver el arbol con `MyModalManagerProvider`:
132
+
133
+ ```tsx
134
+ import { MyModalManagerProvider } from "src/contexts/MyModalManagerContext";
135
+
136
+ <MyModalManagerProvider loadModals={["domain.myModal"]}>
137
+ {children}
138
+ </MyModalManagerProvider>
139
+ ```
140
+
141
+ - Evitar providers anidados salvo que haya una razon real: un provider interno aisla estado y puede cerrar o reemplazar modales inesperadamente.
142
+ - Invocar siempre con `useModalManager` desde `lkd-web-kit`, nunca importando el modal directo desde una vista:
143
+
144
+ ```tsx
145
+ import { useModalManager } from "lkd-web-kit";
146
+
147
+ const { showModal, closeModal } = useModalManager();
148
+ showModal("domain.myModal", { itemId: "123" });
149
+ closeModal("domain.myModal");
150
+ ```
151
+
152
+ ## Formularios Dentro de Modales
153
+
154
+ - Usar la skill `rhf-lkd-forms` si el modal contiene formulario.
155
+ - Pasar `onSuccess={modalProps.onClose}` o cerrar en el submit exitoso.
156
+ - En errores de API, mostrar la alerta esperada y luego hacer `throw error`.
157
+ - No poner mensajes textuales en reglas base de Zod.
158
+
159
+ ## Checklist
160
+
161
+ - El modal exporta default envuelto con `withModalManager`.
162
+ - Las props publicas estan exportadas y registradas en `dynamicModals`.
163
+ - `showModal("key", props)` queda tipado sin `any`.
164
+ - `useModalManager` se importa desde `lkd-web-kit`.
165
+ - La ruta esta bajo `PageProviders` o `MyModalManagerProvider`.
166
+ - `loadModals` se agrega solo cuando conviene precargar.
167
+ - Se reutiliza el modal interno del proyecto salvo necesidad concreta de Mantine directo.
168
+ - Se ejecuta una verificacion focalizada si el cambio no es trivial.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Create Modal Component"
3
+ short_description: "Create project modals with manager registration."
4
+ default_prompt: "Use $create-modal-component to create a modal following this project's modal manager pattern."
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: create-svg-icon
3
+ description: Create SVG icon files in projects that expose SVGs from src/icons. Use when Codex must add a new icon under src/icons from user-provided SVG markup, normalize SVG colors to currentColor for Tailwind text-* styling, and export the icon alias from src/icons/index.ts.
4
+ ---
5
+
6
+ # Create SVG Icon
7
+
8
+ Use this skill when the user provides SVG markup and the desired icon name, usually in the form `Icon{Name}`.
9
+
10
+ ## Required Workflow
11
+
12
+ 1. Read `src/icons/index.ts` before editing.
13
+ 2. Determine the component export name from the user input.
14
+ - Expected format: `Icon{Name}`.
15
+ - Keep the export alias with the `Icon` prefix.
16
+ 3. Verify the icon does not already exist.
17
+ - Check for the exact alias in `src/icons/index.ts`.
18
+ - Check for the target SVG filename in `src/icons/`.
19
+ - If either exists, cancel the operation and notify the user.
20
+ 4. Build the SVG filename.
21
+ - Remove the leading `Icon` prefix.
22
+ - Convert the remaining name to kebab-case.
23
+ - Save it as `src/icons/{name}.svg`.
24
+ - Example: `IconBuildingTower` -> `src/icons/building-tower.svg`.
25
+ 5. Normalize the provided SVG.
26
+ - Replace every explicit visual color value with `currentColor`.
27
+ - Include colors expressed as hex, rgb(), rgba(), hsl(), hsla(), named colors, or inline style values.
28
+ - Apply this to attributes such as `fill`, `stroke`, `color`, `stop-color`, and CSS declarations inside `style`.
29
+ - Preserve `fill="none"`, `stroke="none"`, `currentColor`, `transparent`, masks, clipping, sizing, viewBox, paths, and structural SVG attributes.
30
+ - Do not convert opacity values to `currentColor`.
31
+ 6. Create the SVG file in `src/icons/`.
32
+ 7. Add the alias in `src/icons/index.ts` following the existing export style in that file.
33
+ 8. Report the created SVG path and the added export alias.
34
+
35
+ ## Guardrails
36
+
37
+ - Do not use `any`.
38
+ - Do not create React wrapper components for the icon unless the existing icon system requires it.
39
+ - Do not overwrite an existing icon file or alias.
40
+ - Do not invent SVG path data; use the SVG supplied by the user.
41
+ - Keep edits scoped to `src/icons/{name}.svg` and `src/icons/index.ts` unless the user asks for something else.
@@ -0,0 +1,3 @@
1
+ display_name: Create SVG Icon
2
+ short_description: Create project SVG icons with currentColor and index exports.
3
+ default_prompt: Create an SVG icon for this project from the SVG markup and icon name I provide.
@@ -0,0 +1,187 @@
1
+ ---
2
+ name: create-table-component
3
+ description: Crear, extraer o refactorizar tablas/listados tabulares en proyectos internos usando el patron estricto de componente encapsulado con MyTable, TableWrapper y createColumnHelper. Usar cuando Codex deba mostrar datos en tabla, mover columnas/hook de servicio a un componente de tabla, agregar paginacion, header/footer de tabla, acciones por fila, tablas verticales o adaptar una vista para no usar MyTablePagination.
4
+ ---
5
+
6
+ # Create Table Component
7
+
8
+ Usar este skill para crear o refactorizar tablas del proyecto.
9
+
10
+ ## Workflow obligatorio
11
+
12
+ 1. Leer primero `docs/local-components.md` y `docs/lkd-web-kit.md`.
13
+ 2. Si toca Next.js, leer la documentacion relevante en `node_modules/next/dist/docs/`.
14
+ 3. Revisar un ejemplo cercano antes de editar; preferir `Table{EntityPlural}.tsx`.
15
+ 4. Crear o mantener la tabla en un componente encapsulado junto a la vista/feature que la consume.
16
+ 5. Usar `createColumnHelper<RowData>()`; no declarar `any`.
17
+ 6. Usar `MyTable` para renderizar la tabla.
18
+ 7. Usar `TableWrapper` como raiz visual de la tabla.
19
+ 8. No usar `MyTablePagination` para nuevas tablas.
20
+
21
+ ## Checklist antes de escribir columnas
22
+
23
+ - Definir si la tabla es horizontal o `variant="vertical"`.
24
+ - Definir que representa cada header: solo deben ser columnas/campos reales de la tabla.
25
+ - Distinguir contenido superior/inferior de estructura tabular: titulos, subtitulos, emails, filtros, secciones y acciones van en `TableWrapperHeader`/`TableWrapperFooter`, no en columnas falsas.
26
+ - Definir si la data representa registros completos o pares label/value. Para `variant="vertical"`, preferir registros completos con campos reales.
27
+
28
+ ## Decisiones que hay que preguntar
29
+
30
+ Si el usuario no lo especifica y no se puede inferir del codigo cercano, preguntar:
31
+
32
+ - Si la tabla lleva contenido arriba de la tabla.
33
+ - Si la tabla lleva contenido abajo de la tabla.
34
+ - Si la tabla lleva paginacion.
35
+ - Si la tabla debe ser horizontal o `variant="vertical"`.
36
+
37
+ No asumir que `TableWrapperHeader` implica titulo. Usarlo para cualquier contenido superior: titulo, filtros, acciones, botones, tabs, resumen, subtitulo, email o encabezado de seccion.
38
+
39
+ No asumir que `TableWrapperFooter` implica paginacion. Usarlo para cualquier contenido inferior: paginacion, totales, leyendas, acciones o resumen.
40
+
41
+ Si hay titulo, usar `TableWrapperTitle` dentro de `TableWrapperHeader`.
42
+
43
+ Si hay paginacion, usar `TableWrapperPagination` dentro de `TableWrapperFooter`.
44
+
45
+ ## Paginacion
46
+
47
+ - Usar `TableWrapperPagination`, nunca `MyTablePagination` en tablas nuevas.
48
+ - Usar `variant="short"` para paginacion compacta con texto tipo `1-10 de 15 filas`; queda alineada a la derecha por defecto.
49
+ - Para `variant="short"`, pasar siempre `pageSize`. Si los datos vienen async, usar un fallback estable como `pageSize={currentPage?.pageSize ?? 10}`.
50
+ - No armar el texto de rango en la vista si alcanza con metadata: pasar `totalRows`, `pageSize` y `currentRows`.
51
+ - Usar `text` solo como override manual del texto calculado.
52
+ - Mantener `variant="long"` o sin `variant` para la paginacion completa.
53
+
54
+ ## Tablas verticales
55
+
56
+ Para `MyTable variant="vertical"`, modelar los campos como columnas reales y la data como una fila/registro completo. Los nombres de campos van en `column.header`; los valores van en `column.cell`.
57
+
58
+ No simular una tabla vertical con una columna `entry`, `{ field, value }` o un `grid` dentro de una celda.
59
+
60
+ Ejemplo:
61
+
62
+ ```tsx
63
+ type UserInformationRow = {
64
+ name: string | undefined;
65
+ lastName: string | undefined;
66
+ email: string | undefined;
67
+ };
68
+
69
+ const columnHelper = createColumnHelper<UserInformationRow>();
70
+
71
+ const columns = [
72
+ columnHelper.accessor("name", {
73
+ header: "Nombre",
74
+ cell: (cell) => cell.getValue() ?? "-",
75
+ }),
76
+ columnHelper.accessor("lastName", {
77
+ header: "Apellido",
78
+ cell: (cell) => cell.getValue() ?? "-",
79
+ }),
80
+ columnHelper.accessor("email", {
81
+ header: "Email",
82
+ cell: (cell) => cell.getValue() ?? "-",
83
+ }),
84
+ ];
85
+
86
+ <TableWrapper>
87
+ <TableWrapperHeader>
88
+ <TableWrapperTitle>Informacion del usuario</TableWrapperTitle>
89
+ <span>{user.email}</span>
90
+ <div>Datos personales</div>
91
+ </TableWrapperHeader>
92
+ <MyTable columns={columns} data={[user]} variant="vertical" />
93
+ </TableWrapper>;
94
+ ```
95
+
96
+ ## Encapsulacion
97
+
98
+ - Mover `columns` al componente `Table{EntityPlural}`.
99
+ - Si la tabla consulta datos, mover el hook/servicio al componente de tabla.
100
+ - Si la tabla pagina, mover `pageIndex` y `setPageIndex` al componente de tabla.
101
+ - El padre solo debe pasar entradas externas reales: filtros ya aplicados, ids, callbacks o flags.
102
+ - Al cambiar filtros externos, resetear la pagina dentro del componente de tabla con `useEffect` si aplica.
103
+
104
+ ## Patron base
105
+
106
+ ```tsx
107
+ "use client";
108
+
109
+ import { createColumnHelper } from "@tanstack/react-table";
110
+ import { useMemo, useState } from "react";
111
+ import MyTable from "src/components/ui/MyTable";
112
+ import TableWrapper, {
113
+ TableWrapperFooter,
114
+ TableWrapperHeader,
115
+ TableWrapperPagination,
116
+ TableWrapperTitle,
117
+ } from "src/components/ui/TableWrapper";
118
+
119
+ type ItemTableData = {
120
+ id: string;
121
+ name: string;
122
+ };
123
+
124
+ type TableItemsProps = {
125
+ selectedFilters: Record<string, unknown>;
126
+ };
127
+
128
+ const columnHelper = createColumnHelper<ItemTableData>();
129
+
130
+ const TableItems = ({ selectedFilters }: TableItemsProps) => {
131
+ const [pageIndex, setPageIndex] = useState(0);
132
+
133
+ const columns = useMemo(
134
+ () => [
135
+ columnHelper.accessor("name", {
136
+ header: "Nombre",
137
+ cell: (cell) => cell.getValue(),
138
+ }),
139
+ ],
140
+ [],
141
+ );
142
+
143
+ return (
144
+ <TableWrapper>
145
+ <TableWrapperHeader>
146
+ <TableWrapperTitle>Items</TableWrapperTitle>
147
+ </TableWrapperHeader>
148
+ <MyTable columns={columns} data={[]} />
149
+ <TableWrapperFooter>
150
+ <TableWrapperPagination
151
+ total={1}
152
+ value={pageIndex + 1}
153
+ onChange={(page) => setPageIndex(page - 1)}
154
+ />
155
+ </TableWrapperFooter>
156
+ </TableWrapper>
157
+ );
158
+ };
159
+
160
+ export default TableItems;
161
+ ```
162
+
163
+ Ejemplo short:
164
+
165
+ ```tsx
166
+ <TableWrapperPagination
167
+ variant="short"
168
+ total={pageData?.total.pages ?? 1}
169
+ value={(pageData?.currentPage.pageIndex ?? 0) + 1}
170
+ totalRows={pageData?.total.elements}
171
+ pageSize={pageData?.currentPage.pageSize ?? 10}
172
+ currentRows={pageData?.currentPage.elements}
173
+ onChange={(page) => setPageIndex(page - 1)}
174
+ />;
175
+ ```
176
+
177
+ ## Antipatrones
178
+
179
+ - No dejar columnas o hooks de servicio de la tabla en la vista padre.
180
+ - No crear wrappers nuevos si `TableWrapper` y `MyTable` alcanzan.
181
+ - No usar `Paper` directamente para la tabla cuando el patron pide `TableWrapper`.
182
+ - No usar `MyTablePagination` en codigo nuevo.
183
+ - No usar `paginationVariant` ni `paginationText` en `TableWrapperPagination`; usar `variant` y `text`.
184
+ - No calcular `1-10 de 15 filas` en la vista si `TableWrapperPagination` puede calcularlo.
185
+ - No agregar abstracciones compartidas antes de tener reutilizacion real.
186
+ - No crear `MyTableVertical` si `MyTable variant="vertical"` resuelve el caso.
187
+ - No poner titulos de seccion como "Datos personales" o "Informacion del usuario" como headers de columna.
@@ -0,0 +1,3 @@
1
+ display_name: Create Table Component
2
+ short_description: Create encapsulated project tables with MyTable and TableWrapper.
3
+ default_prompt: Create or refactor a table component using this project's MyTable and TableWrapper pattern.
@@ -0,0 +1,186 @@
1
+ ---
2
+ name: rhf-lkd-forms
3
+ description: Crear y revisar formularios, filtros, campos controlados, integraciones con React Hook Form, validaciones Zod, reglas superRefine, botones de submit y patrones Form* de lkd-web-kit. Usar cuando Codex trabaje en interfaces de formularios, filtros, wrappers de campos, schemas, valores iniciales, mapeo de payloads, flujos de submit o comportamiento de validación en proyectos que usan React Hook Form, Zod y lkd-web-kit.
4
+ ---
5
+
6
+ # Formularios RHF + lkd-web-kit
7
+
8
+ Usar este skill para formularios, filtros y campos controlados construidos con React Hook Form, Zod y `lkd-web-kit`.
9
+
10
+ ## Workflow obligatorio
11
+
12
+ 1. Leer primero la documentación de formularios del proyecto: `docs/lkd-web-kit.md` o el equivalente local.
13
+ 2. Reutilizar la familia `Form*` existente antes de crear algo nuevo:
14
+ `Form`, `FormTextInput`, `FormSelect`, `FormMultiSelect`, `FormTextarea`,
15
+ `FormNumberInput`, `FormMonthPickerInput`, `FormCheckbox`,
16
+ `FormRadioGroup`, `FormSubmitButton` y wrappers cercanos del proyecto.
17
+ 3. Importar primero desde `lkd-web-kit`. Usar wrappers locales solo cuando el proyecto ya los tenga y agreguen estilos o comportamiento propio.
18
+ 4. No crear un wrapper nuevo si el kit o el proyecto ya tienen un campo equivalente.
19
+ 5. Para formularios con validación, usar `useForm` con `zodResolver(schema)`.
20
+ 6. Pasar `mode` solo cuando la API del componente lo soporte. No inventar props.
21
+
22
+ ## Estructura recomendada
23
+
24
+ - Mantener formularios triviales inline.
25
+ - Cuando un formulario mapea datos de API o construye un payload, separar lo repetitivo:
26
+ - `schema`: objeto Zod y tipo inferido.
27
+ - `createDefaultValues`: datos de API/dominio a valores del formulario.
28
+ - `createPayload`: valores del formulario a payload de API.
29
+ - Usar `z.input<typeof schema>` para defaults cuando haya transforms o preprocess.
30
+ - Usar `z.infer<typeof schema>` para datos ya validados en submit.
31
+ - Colocar el código privado junto a la ruta o feature, salvo que sea realmente compartido.
32
+
33
+ ## Validaciones con Zod
34
+
35
+ - Poner reglas de campo en el schema base.
36
+ - No poner mensajes textuales en reglas base de Zod como `min(1, "Texto")`; usar la configuración global de errores o la capa de traducciones del proyecto.
37
+ - Usar `superRefine` para reglas cruzadas entre campos y reglas de negocio.
38
+ - En `superRefine`, asociar el issue al campo que el usuario puede corregir usando `path`.
39
+ - Permitir mensajes custom en `superRefine` cuando el proyecto no tenga un mapeo de códigos para esa regla de negocio.
40
+ - Preferir `ctx.addIssue(...)`; usar `ctx.issues.push(...)` solo si ya es el patrón local.
41
+
42
+ ## Campos y filtros
43
+
44
+ - Todo input que modifique estado de React Hook Form debe ser un componente `Form*`.
45
+ - Usar `My*` o inputs Mantine solo para UI standalone fuera de React Hook Form.
46
+ - Preferir componentes de campo del proyecto, como `NameField` o `StatusField`, cuando ya provean `name`, labels, opciones traducidas o carga de datos.
47
+ - Los filtros siguen el mismo patrón: `useForm` + `Form` + `Form*`.
48
+ - El submit de filtros puede actualizar query params, estado local o estado del padre según el proyecto; no agregar una librería de estado solo por un filtro.
49
+ - Si un campo client lee catálogos o datos cacheados, verificar que la página o el padre carguen esos datos según la convención del proyecto.
50
+
51
+ ## Submit y errores
52
+
53
+ - Usar `FormSubmitButton` para acciones de submit. No usar aliases como `FormButtonSubmit` salvo que el proyecto realmente los exporte.
54
+ - Dejar que `FormSubmitButton` maneje el loading de submit mediante React Hook Form.
55
+ - En submits async, mostrar la notificación de éxito o error esperada por el proyecto.
56
+ - En `catch`, mostrar la notificación de error y luego hacer `throw error` para que el wrapper del formulario pueda reaccionar visualmente.
57
+ - Recortar o normalizar valores en el límite donde se arma el payload de API, no en handlers sueltos de campos.
58
+
59
+ ## Antipatrones
60
+
61
+ - No usar `register` directamente para campos ya cubiertos por `Form*`.
62
+ - No duplicar wrappers tipo `FormTextInput` o `FormSelect`.
63
+ - No usar `any`; tipar valores del formulario desde el schema.
64
+ - No agregar helpers custom de validación cuando Zod ya cubre la regla.
65
+ - No mezclar campos Mantine no controlados dentro de un formulario de React Hook Form.
66
+ - No agregar dependencias nuevas para estado de formulario, validación de campos o filtros comunes.
67
+
68
+ ## Ejemplos mínimos
69
+
70
+ Formulario de edición:
71
+
72
+ ```tsx
73
+ "use client";
74
+
75
+ import { zodResolver } from "@hookform/resolvers/zod";
76
+ import {
77
+ FormSelect,
78
+ FormSubmitButton,
79
+ FormTextInput,
80
+ } from "lkd-web-kit";
81
+ import { useForm } from "react-hook-form";
82
+ import z from "zod";
83
+ import Form from "src/components/form/Form";
84
+
85
+ const editSchema = z.object({
86
+ name: z.string().min(1),
87
+ status: z.string().nullable(),
88
+ });
89
+
90
+ type EditValues = z.infer<typeof editSchema>;
91
+
92
+ const createDefaultValues = (item: { name: string; status: string | null }): EditValues => ({
93
+ name: item.name,
94
+ status: item.status,
95
+ });
96
+
97
+ const createPayload = (data: EditValues) => ({
98
+ name: data.name.trim(),
99
+ status: data.status || undefined,
100
+ });
101
+
102
+ export function EditItemForm({ item }: { item: { name: string; status: string | null } }) {
103
+ const methods = useForm<EditValues>({
104
+ defaultValues: createDefaultValues(item),
105
+ resolver: zodResolver(
106
+ editSchema.superRefine((data, ctx) => {
107
+ if (data.status === "published" && data.name.trim().length < 3) {
108
+ ctx.addIssue({
109
+ code: "custom",
110
+ message: "El nombre es demasiado corto para publicar",
111
+ path: ["name"],
112
+ });
113
+ }
114
+ }),
115
+ ),
116
+ });
117
+
118
+ return (
119
+ <Form
120
+ methods={methods}
121
+ onSubmit={async (data) => {
122
+ try {
123
+ await updateItem(createPayload(data));
124
+ } catch (error) {
125
+ showErrorNotification();
126
+ throw error;
127
+ }
128
+ }}
129
+ >
130
+ <FormTextInput name="name" label="Nombre" />
131
+ <FormSelect
132
+ name="status"
133
+ label="Estado"
134
+ data={[
135
+ { value: "draft", label: "Borrador" },
136
+ { value: "published", label: "Publicado" },
137
+ ]}
138
+ />
139
+ <FormSubmitButton>Guardar</FormSubmitButton>
140
+ </Form>
141
+ );
142
+ }
143
+ ```
144
+
145
+ Filtro:
146
+
147
+ ```tsx
148
+ "use client";
149
+
150
+ import { FormSelect, FormSubmitButton, FormTextInput } from "lkd-web-kit";
151
+ import { useForm } from "react-hook-form";
152
+ import Form from "src/components/form/Form";
153
+
154
+ type FilterValues = {
155
+ search: string;
156
+ status: string | null;
157
+ };
158
+
159
+ export function ItemFilters({
160
+ onApply,
161
+ }: {
162
+ onApply: (values: FilterValues) => void;
163
+ }) {
164
+ const methods = useForm<FilterValues>({
165
+ defaultValues: {
166
+ search: "",
167
+ status: null,
168
+ },
169
+ });
170
+
171
+ return (
172
+ <Form methods={methods} onSubmit={onApply}>
173
+ <FormTextInput name="search" label="Buscar" />
174
+ <FormSelect
175
+ name="status"
176
+ label="Estado"
177
+ data={[
178
+ { value: "draft", label: "Borrador" },
179
+ { value: "published", label: "Publicado" },
180
+ ]}
181
+ />
182
+ <FormSubmitButton>Aplicar</FormSubmitButton>
183
+ </Form>
184
+ );
185
+ }
186
+ ```
@@ -0,0 +1,73 @@
1
+ "use client";
2
+ require("../../_virtual/_rolldown/runtime.cjs");
3
+ let react = require("react");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ //#region src/contexts/ModalManagerContext/index.tsx
6
+ var ModalManagerContext = (0, react.createContext)(void 0);
7
+ var ModalManagerProvider = ({ children, modals, loadModals = [] }) => {
8
+ const [statusModal, setStatusModal] = (0, react.useState)([]);
9
+ (0, react.useEffect)(() => {
10
+ loadModals.forEach((modalKey) => {
11
+ Object.keys(modals).filter((key) => key.startsWith(modalKey)).forEach((key) => {
12
+ const item = modals[key];
13
+ item.load?.().then((modal) => {
14
+ item.component = modal.default;
15
+ });
16
+ });
17
+ });
18
+ }, [loadModals, modals]);
19
+ const showModal = (key, props, options) => {
20
+ const { multiple, ...modalProps } = options ?? {};
21
+ const modalState = {
22
+ modalKey: key,
23
+ props,
24
+ modalProps,
25
+ opened: true
26
+ };
27
+ setStatusModal((prev) => multiple ? [...prev, modalState] : [modalState]);
28
+ };
29
+ const closeModal = (key) => {
30
+ if (key === void 0) {
31
+ setStatusModal((prev) => prev.map((modal) => ({
32
+ ...modal,
33
+ opened: false
34
+ })));
35
+ return;
36
+ }
37
+ setStatusModal((prev) => prev.map((modal) => modal.modalKey === key ? {
38
+ ...modal,
39
+ opened: false
40
+ } : modal));
41
+ };
42
+ const renderModals = (0, react.useMemo)(() => statusModal.map(({ modalKey, props, opened, modalProps }, i) => {
43
+ const Component = modals[modalKey]?.component;
44
+ if (!Component) return null;
45
+ return /* @__PURE__ */ (0, react.createElement)(Component, {
46
+ ...props,
47
+ key: `${modalKey}-${i}`,
48
+ modalProps: {
49
+ ...modalProps,
50
+ opened,
51
+ zIndex: 200 + i
52
+ },
53
+ removeModal: () => {
54
+ setStatusModal((prev) => prev.filter((_, index) => index !== i));
55
+ }
56
+ });
57
+ }), [statusModal, modals]);
58
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ModalManagerContext.Provider, {
59
+ value: {
60
+ showModal,
61
+ closeModal
62
+ },
63
+ children: [renderModals, children]
64
+ });
65
+ };
66
+ var useModalManager = () => {
67
+ const context = (0, react.use)(ModalManagerContext);
68
+ if (context === void 0) throw Error("Out of context: useModalManager");
69
+ return context;
70
+ };
71
+ //#endregion
72
+ exports.ModalManagerProvider = ModalManagerProvider;
73
+ exports.useModalManager = useModalManager;
@@ -0,0 +1,35 @@
1
+ import { ModalProps } from '@mantine/core';
2
+ import { ComponentType, ReactNode } from 'react';
3
+ import { ModalManagerWrapperProps } from '../../hocs';
4
+ export interface ModalRegistryItem<Props extends object = object> {
5
+ component?: ComponentType<ModalManagerWrapperProps<object>>;
6
+ load?: () => Promise<{
7
+ default: ComponentType<ModalManagerWrapperProps<object>>;
8
+ }>;
9
+ path?: string;
10
+ readonly __props?: Props;
11
+ }
12
+ export type ModalRegistryRecord = Record<string, ModalRegistryItem>;
13
+ export interface ModalManagerRegistryOverride {
14
+ }
15
+ export type ModalRegistry = ModalManagerRegistryOverride extends {
16
+ modals: infer Registry extends ModalRegistryRecord;
17
+ } ? Registry : ModalRegistryRecord;
18
+ export type ModalKey = Extract<keyof ModalRegistry, string>;
19
+ export type ShowModalOptions = {
20
+ multiple?: boolean;
21
+ } & Partial<ModalProps>;
22
+ export interface ModalManagerProviderProps {
23
+ children: ReactNode;
24
+ modals: ModalRegistry;
25
+ loadModals?: (ModalKey | (string & {}))[];
26
+ }
27
+ type PublicModalProps<Key extends ModalKey> = ModalRegistry[Key] extends ModalRegistryItem<infer Props> ? Props : never;
28
+ type ModalManagerValues = {
29
+ showModal: <Key extends ModalKey>(key: Key, props?: PublicModalProps<Key>, options?: ShowModalOptions) => void;
30
+ closeModal: (key?: ModalKey) => void;
31
+ };
32
+ export declare const ModalManagerProvider: ({ children, modals, loadModals, }: ModalManagerProviderProps) => import("react/jsx-runtime").JSX.Element;
33
+ export declare const useModalManager: () => ModalManagerValues;
34
+ export {};
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/contexts/ModalManagerContext/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAErD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAExD,MAAM,WAAW,iBAAiB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IAC9D,SAAS,CAAC,EAAE,aAAa,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC,CAAA;IAC3D,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,aAAa,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC,CAAA;KAAE,CAAC,CAAA;IAClF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAA;CACzB;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;AAEnE,MAAM,WAAW,4BAA4B;CAAG;AAEhD,MAAM,MAAM,aAAa,GAAG,4BAA4B,SAAS;IAC/D,MAAM,EAAE,MAAM,QAAQ,SAAS,mBAAmB,CAAA;CACnD,GACG,QAAQ,GACR,mBAAmB,CAAA;AAEvB,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,aAAa,EAAE,MAAM,CAAC,CAAA;AAE3D,MAAM,MAAM,gBAAgB,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;AAE3E,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,SAAS,CAAA;IACnB,MAAM,EAAE,aAAa,CAAA;IACrB,UAAU,CAAC,EAAE,CAAC,QAAQ,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,EAAE,CAAA;CAC1C;AAED,KAAK,gBAAgB,CAAC,GAAG,SAAS,QAAQ,IACxC,aAAa,CAAC,GAAG,CAAC,SAAS,iBAAiB,CAAC,MAAM,KAAK,CAAC,GAAG,KAAK,GAAG,KAAK,CAAA;AAS3E,KAAK,kBAAkB,GAAG;IACxB,SAAS,EAAE,CAAC,GAAG,SAAS,QAAQ,EAC9B,GAAG,EAAE,GAAG,EACR,KAAK,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,EAC7B,OAAO,CAAC,EAAE,gBAAgB,KACvB,IAAI,CAAA;IACT,UAAU,EAAE,CAAC,GAAG,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAA;CACrC,CAAA;AAID,eAAO,MAAM,oBAAoB,GAAI,mCAIlC,yBAAyB,4CAmE3B,CAAA;AAED,eAAO,MAAM,eAAe,0BAM3B,CAAA"}
@@ -0,0 +1,71 @@
1
+ "use client";
2
+ import { createContext, createElement, use, useEffect, useMemo, useState } from "react";
3
+ import { jsxs } from "react/jsx-runtime";
4
+ //#region src/contexts/ModalManagerContext/index.tsx
5
+ var ModalManagerContext = createContext(void 0);
6
+ var ModalManagerProvider = ({ children, modals, loadModals = [] }) => {
7
+ const [statusModal, setStatusModal] = useState([]);
8
+ useEffect(() => {
9
+ loadModals.forEach((modalKey) => {
10
+ Object.keys(modals).filter((key) => key.startsWith(modalKey)).forEach((key) => {
11
+ const item = modals[key];
12
+ item.load?.().then((modal) => {
13
+ item.component = modal.default;
14
+ });
15
+ });
16
+ });
17
+ }, [loadModals, modals]);
18
+ const showModal = (key, props, options) => {
19
+ const { multiple, ...modalProps } = options ?? {};
20
+ const modalState = {
21
+ modalKey: key,
22
+ props,
23
+ modalProps,
24
+ opened: true
25
+ };
26
+ setStatusModal((prev) => multiple ? [...prev, modalState] : [modalState]);
27
+ };
28
+ const closeModal = (key) => {
29
+ if (key === void 0) {
30
+ setStatusModal((prev) => prev.map((modal) => ({
31
+ ...modal,
32
+ opened: false
33
+ })));
34
+ return;
35
+ }
36
+ setStatusModal((prev) => prev.map((modal) => modal.modalKey === key ? {
37
+ ...modal,
38
+ opened: false
39
+ } : modal));
40
+ };
41
+ const renderModals = useMemo(() => statusModal.map(({ modalKey, props, opened, modalProps }, i) => {
42
+ const Component = modals[modalKey]?.component;
43
+ if (!Component) return null;
44
+ return /* @__PURE__ */ createElement(Component, {
45
+ ...props,
46
+ key: `${modalKey}-${i}`,
47
+ modalProps: {
48
+ ...modalProps,
49
+ opened,
50
+ zIndex: 200 + i
51
+ },
52
+ removeModal: () => {
53
+ setStatusModal((prev) => prev.filter((_, index) => index !== i));
54
+ }
55
+ });
56
+ }), [statusModal, modals]);
57
+ return /* @__PURE__ */ jsxs(ModalManagerContext.Provider, {
58
+ value: {
59
+ showModal,
60
+ closeModal
61
+ },
62
+ children: [renderModals, children]
63
+ });
64
+ };
65
+ var useModalManager = () => {
66
+ const context = use(ModalManagerContext);
67
+ if (context === void 0) throw Error("Out of context: useModalManager");
68
+ return context;
69
+ };
70
+ //#endregion
71
+ export { ModalManagerProvider, useModalManager };
@@ -1,3 +1,4 @@
1
+ export * from './ModalManagerContext';
1
2
  export { NavigationHistoryProvider } from './NavigationHistoryContext';
2
3
  export * from './NavigationHistoryContext/hook';
3
4
  export * from './PageDataContext';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contexts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAA;AACtE,cAAc,iCAAiC,CAAA;AAC/C,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contexts/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAA;AACtE,cAAc,iCAAiC,CAAA;AAC/C,cAAc,mBAAmB,CAAA"}
package/dist/index.cjs CHANGED
@@ -20,9 +20,10 @@ const require_index$15 = require("./components/MyTimeInput/index.cjs");
20
20
  const require_NavItems = require("./components/NavItems.cjs");
21
21
  const require_http_status = require("./consts/http-status.cjs");
22
22
  const require_revalidate = require("./consts/revalidate.cjs");
23
- const require_index$16 = require("./contexts/NavigationHistoryContext/index.cjs");
23
+ const require_index$16 = require("./contexts/ModalManagerContext/index.cjs");
24
+ const require_index$17 = require("./contexts/NavigationHistoryContext/index.cjs");
24
25
  const require_hook = require("./contexts/NavigationHistoryContext/hook.cjs");
25
- const require_index$17 = require("./contexts/PageDataContext/index.cjs");
26
+ const require_index$18 = require("./contexts/PageDataContext/index.cjs");
26
27
  const require_withController = require("./hocs/withController.cjs");
27
28
  const require_withModalManager = require("./hocs/withModalManager.cjs");
28
29
  const require_FormCheckbox = require("./form/base/FormCheckbox.cjs");
@@ -81,6 +82,7 @@ exports.HttpStatus = require_http_status.HttpStatus;
81
82
  exports.Icon = require_Icon.Icon;
82
83
  exports.InfinityLoadMoreButton = require_index$1.InfinityLoadMoreButton;
83
84
  exports.InfinitySelect = require_index$2.InfinitySelect;
85
+ exports.ModalManagerProvider = require_index$16.ModalManagerProvider;
84
86
  exports.MyCheckboxGroup = require_index$3.MyCheckboxGroup;
85
87
  exports.MyDateInput = require_index$4.MyDateInput;
86
88
  exports.MyDatePickerInput = require_index$5.MyDatePickerInput;
@@ -95,8 +97,8 @@ exports.MyTextInput = require_index$14.MyTextInput;
95
97
  exports.MyTextarea = require_index$13.MyTextarea;
96
98
  exports.MyTimeInput = require_index$15.MyTimeInput;
97
99
  exports.NavItems = require_NavItems.NavItems;
98
- exports.NavigationHistoryProvider = require_index$16.NavigationHistoryProvider;
99
- exports.PageDataProvider = require_index$17.PageDataProvider;
100
+ exports.NavigationHistoryProvider = require_index$17.NavigationHistoryProvider;
101
+ exports.PageDataProvider = require_index$18.PageDataProvider;
100
102
  exports.QP_BACK_URL_NAME = require_hook.QP_BACK_URL_NAME;
101
103
  exports.Revalidate = require_revalidate.Revalidate;
102
104
  exports.addBodyJsonHook = require_addBodyJsonHook.addBodyJsonHook;
@@ -120,9 +122,10 @@ exports.timeInputToNumber = require_FormTimeInput.timeInputToNumber;
120
122
  exports.toTailwindColors = require_to_tailwind_colors.toTailwindColors;
121
123
  exports.useBreakpoint = require_useBreakpoint.useBreakpoint;
122
124
  exports.useFetchNextPageOnScroll = require_useFetchNextPageOnScroll.useFetchNextPageOnScroll;
125
+ exports.useModalManager = require_index$16.useModalManager;
123
126
  exports.useNavigationHistory = require_hook.useNavigationHistory;
124
127
  exports.useOnScrollProgress = require_useOnScrollProgress.useOnScrollProgress;
125
- exports.usePageData = require_index$17.usePageData;
128
+ exports.usePageData = require_index$18.usePageData;
126
129
  exports.useUpdateSearchParams = require_useUpdateSearchParams.useUpdateSearchParams;
127
130
  exports.useZodConfig = require_useZodConfig.useZodConfig;
128
131
  exports.withController = require_withController.withController;
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ import { MyTimeInput } from "./components/MyTimeInput/index.js";
19
19
  import { NavItems } from "./components/NavItems.js";
20
20
  import { HttpStatus } from "./consts/http-status.js";
21
21
  import { Revalidate } from "./consts/revalidate.js";
22
+ import { ModalManagerProvider, useModalManager } from "./contexts/ModalManagerContext/index.js";
22
23
  import { NavigationHistoryProvider } from "./contexts/NavigationHistoryContext/index.js";
23
24
  import { QP_BACK_URL_NAME, useNavigationHistory } from "./contexts/NavigationHistoryContext/hook.js";
24
25
  import { PageDataProvider, usePageData } from "./contexts/PageDataContext/index.js";
@@ -59,4 +60,4 @@ import { addBodyJsonHook } from "./utils/ky/addBodyJsonHook.js";
59
60
  import { parseJSON } from "./utils/ky/parseJson.js";
60
61
  import { queryStringify } from "./utils/query-stringify.js";
61
62
  import { createNewRoute, newRoute } from "./utils/new-route.js";
62
- export { EmptyState, Form, FormCheckbox, FormCheckboxGroup, FormDateInput, FormDatePickerInput, FormDateTimePicker, FormInfinitySelect, FormMonthPickerInput, FormMultiSelect, FormNumberInput, FormRadioGroup, FormSelect, FormSubmitButton, FormTextInput, FormTextarea, FormTimeInput, HttpStatus, Icon, InfinityLoadMoreButton, InfinitySelect, MyCheckboxGroup, MyDateInput, MyDatePickerInput, MyDateTimePicker, MyMonthPickerInput, MyMultiSelect, MyNotifications, MyNumberInput, MyRadioGroup, MySelect, MyTextInput, MyTextarea, MyTimeInput, NavItems, NavigationHistoryProvider, PageDataProvider, QP_BACK_URL_NAME, Revalidate, addBodyJsonHook, breakpointsWithPx, createNewRoute, formatBytes, getVirtualContainerProps, getVirtualItemProps, groupBy, indexBy, isInfinityEmpty, myDefaultTheme, newRoute, nullableButRequired, numberToTimeInput, optionalButRequired, parseJSON, queryStringify, shuffleArray, timeInputToNumber, toTailwindColors, useBreakpoint, useFetchNextPageOnScroll, useNavigationHistory, useOnScrollProgress, usePageData, useUpdateSearchParams, useZodConfig, withController, withModalManager, zodValidator };
63
+ export { EmptyState, Form, FormCheckbox, FormCheckboxGroup, FormDateInput, FormDatePickerInput, FormDateTimePicker, FormInfinitySelect, FormMonthPickerInput, FormMultiSelect, FormNumberInput, FormRadioGroup, FormSelect, FormSubmitButton, FormTextInput, FormTextarea, FormTimeInput, HttpStatus, Icon, InfinityLoadMoreButton, InfinitySelect, ModalManagerProvider, MyCheckboxGroup, MyDateInput, MyDatePickerInput, MyDateTimePicker, MyMonthPickerInput, MyMultiSelect, MyNotifications, MyNumberInput, MyRadioGroup, MySelect, MyTextInput, MyTextarea, MyTimeInput, NavItems, NavigationHistoryProvider, PageDataProvider, QP_BACK_URL_NAME, Revalidate, addBodyJsonHook, breakpointsWithPx, createNewRoute, formatBytes, getVirtualContainerProps, getVirtualItemProps, groupBy, indexBy, isInfinityEmpty, myDefaultTheme, newRoute, nullableButRequired, numberToTimeInput, optionalButRequired, parseJSON, queryStringify, shuffleArray, timeInputToNumber, toTailwindColors, useBreakpoint, useFetchNextPageOnScroll, useModalManager, useNavigationHistory, useOnScrollProgress, usePageData, useUpdateSearchParams, useZodConfig, withController, withModalManager, zodValidator };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lkd-web-kit",
3
- "version": "0.9.2",
3
+ "version": "0.10.1",
4
4
  "description": "A template for creating React component libraries with Vite.",
5
5
  "author": "LKD",
6
6
  "license": "MIT",
@@ -19,11 +19,14 @@
19
19
  "import": "./dist/index.js"
20
20
  }
21
21
  },
22
+ "bin": {
23
+ "lkd-install-codex-skills": "./scripts/install-codex-skills.mjs"
24
+ },
22
25
  "sideEffects": false,
23
26
  "engines": {
24
27
  "node": ">=22.12.0"
25
28
  },
26
- "files": ["dist"],
29
+ "files": ["dist", "codex/skills", "scripts/install-codex-skills.mjs"],
27
30
  "scripts": {
28
31
  "test": "vitest run --passWithNoTests",
29
32
  "test:watch": "vitest",
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { cpSync, existsSync, mkdirSync } from 'node:fs'
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const scriptDir = dirname(fileURLToPath(import.meta.url))
7
+ const source = join(scriptDir, '..', 'codex', 'skills')
8
+ const target = join(process.cwd(), '.agents', 'skills')
9
+
10
+ if (!existsSync(source)) {
11
+ throw new Error(`No se encontraron skills en ${source}`)
12
+ }
13
+
14
+ mkdirSync(target, { recursive: true })
15
+ cpSync(source, target, { recursive: true, force: true })
16
+
17
+ console.log(`Skills instaladas en ${target}`)