diff --git a/.claude/parts/providers.md b/.claude/parts/providers.md deleted file mode 100644 index f3b788c6..00000000 --- a/.claude/parts/providers.md +++ /dev/null @@ -1,71 +0,0 @@ -# Providers Reference - -## Categorías - -### Files (`src/providers/files/`) - -- Acceso a filesystem -- Lectura/escritura de archivos - -### HTTP (`src/providers/http/`) - -- **express-server**: Servidor HTTP Express (usado en desarrollo) -- **fastify-server**: Servidor HTTP Fastify con host-based routing (usado en producción) - -### Object (`src/providers/object/`) - -- Almacenamiento de objetos -- Serialización - -## HTTP Providers - -### express-server - -Servidor Express tradicional. Usado automáticamente en modo desarrollo (`npm run dev`). - -### fastify-server - -Servidor Fastify con soporte para virtual hosts. Usado automáticamente en producción (`npm run start` y `npm run start:prodtests`). - -**Características:** - -- Host-based routing (subdominios y dominios) -- Mayor rendimiento que Express -- Prioridad automática (hosts específicos > comodines) -- SPA fallback integrado - -**Puertos:** - -- `npm run start` → puerto 80 -- `npm run start:prodtests` → puerto 3000 -- `npm run dev` → puerto 3000 (con dev servers en puertos separados) - -## Crear nuevo Provider - -```bash -npm run create:provider -``` - -## Uso en Apps - -Declarar en `config.json`: - -```json -{ - "providers": [ - { - "name": "mongo", - "global": true, - "custom": { - "uri": "mongodb://..." - } - } - ] -} -``` - -## Obtener en código - -```typescript -const storage = kernel.getProvider(STORAGE_PROVIDER); -``` diff --git a/.claude/parts/services.md b/.claude/parts/services.md deleted file mode 100644 index 728f5a6d..00000000 --- a/.claude/parts/services.md +++ /dev/null @@ -1,55 +0,0 @@ -# Services Reference - -## Core Services (Modo Kernel) - -Servicios que se cargan automáticamente con `kernelMode: true`. - -### ExecutionManagerService -- Gestión de workers para carga distribuida -- Decorador `@Distributed` para métodos pesados -- Monitoreo de CPU/memoria - -### IdentityManagerService -- Gestión de usuarios, roles y grupos -- 8 roles predefinidos: SYSTEM, Admin, Network Manager, Security Manager, Data Manager, App Manager, Config Manager, User -- Hash PBKDF2 (100k iteraciones) -- Fallback a memoria si MongoDB no disponible - -### LangManagerService -- Internacionalización (i18n) -- Archivos en `/{app}/i18n/{locale}.js` -- Interpolación `{{param}}` -- Endpoints: `/api/i18n/:namespace`, `/api/i18n?namespaces=...` - -### LogManagerService -- Logging centralizado del kernel - -### UIFederationService -- Build y servido de módulos UI -- Soporta: Stencil, React, Vue, Vite, Astro -- Module Federation con Rspack -- Namespaces para múltiples UI libraries -- Service Worker dinámico - -## Data Services - -### Ubicación: `src/services/data/` - -Servicios de acceso a datos (CRUD, etc.) - -## Crear nuevo Service - -```bash -npm run create:service -``` - -## Configuración de Service - -Archivo `config.json` en el directorio del service: - -```json -{ - "kernelMode": false, - "dependencies": [] -} -``` diff --git a/.claude/parts/ui-system.md b/.claude/parts/ui-system.md deleted file mode 100644 index 4858b4a2..00000000 --- a/.claude/parts/ui-system.md +++ /dev/null @@ -1,90 +0,0 @@ -# UI System Reference - -## UI Libraries (Stencil Web Components) - -### 00-web-ui-library - -- Componentes desktop: ``, ``, ``, `` -- Framework-agnostic (Web Components) - -### 00-web-ui-library-mobile - -- Componentes móviles - -## Uso - -```typescript -import '@ui-library/loader'; - -// JSX -Click - -// HTML -Click -``` - -## UIFederationService Config - -```json -{ - "uiModule": { - "name": "layout", - "uiNamespace": "default", - "framework": "react", - "devPort": 3014, - "serviceWorker": true, - "i18n": true, - "hosting": { - "hosts": [{ "domain": "local.com", "subdomains": ["cloud", "users", "*"] }] - } - } -} -``` - -## Hosting (Producción) - -En producción, las apps se sirven por dominio/subdominio en un solo puerto: - -```json -"hosting": { - "hosts": [{ "domain": "local.com", "subdomains": ["cloud", "users", "*"] }] -} -``` - -**Opciones:** -- `hosts`: Lista de dominios con subdominios -- `subdomains`: Lista simple (usa dominio por defecto `local.com`) -- `domains`: Dominios completos - -**Prioridad:** hosts específicos > comodines (`*`) - -## Namespaces - -- Permiten múltiples UI libraries sin colisiones -- Cada namespace tiene su import map: `/:namespace/importmap.json` -- `default` se usa cuando no se especifica - -## i18n - -Archivos en `/{app}/i18n/{locale}.js`: - -```javascript -export default { - title: "Estadísticas", - welcome: "Bienvenido, {{name}}", -}; -``` - -Cliente: - -```javascript -t("welcome", { name: "Juan" }); -setLocale("es"); -getLocale(); -``` - -## Endpoints - -- `GET /api/ui/namespaces` -- `GET /:namespace/importmap.json` -- `GET /api/i18n/:namespace?locale=es` diff --git a/.gitignore b/.gitignore index 3ccbac34..5ebf2b20 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,9 @@ npm-debug.log* .env.development apps/private -**.pyc \ No newline at end of file +**.pyc +__pycache__ + +chroma_db_codebase + +temp-ui \ No newline at end of file diff --git a/.mcp.json b/.mcp.json index 26ce7e1e..0f360716 100644 --- a/.mcp.json +++ b/.mcp.json @@ -7,6 +7,12 @@ "command": "/home/abby/.local/bin/serena-mcp-server", "args": ["--context", "claude-code", "--project", "/home/abby/Escritorio/proyectos/personales/ADC-platform"], "env": {} + }, + "search_code_vector": { + "type": "stdio", + "command": "/home/abby/Escritorio/scripting/ai/rag/venv/bin/python", + "args": ["/home/abby/Escritorio/scripting/ai/rag/rag_server.py"], + "env": {} } }, "enabledMcpjsonServers": [], diff --git a/.serena/memories/task_completion_checklist.md b/.serena/memories/task_completion_checklist.md index 0a6f34d9..da74003a 100644 --- a/.serena/memories/task_completion_checklist.md +++ b/.serena/memories/task_completion_checklist.md @@ -3,52 +3,30 @@ ## Before Considering a Task Complete ### 1. Run TypeScript Check - ```bash npm run typecheck ``` -This runs TypeScript compiler + ts-prune for dead code detection. - ### 2. Run Linting - ```bash npm run lint ``` - Must pass with zero warnings (`--max-warnings 0`). -### 3. Fix Linting Issues (if any) - -```bash -npm run lint:fix -``` - -### 4. Test in Development Mode - +### 3. Test in Development Mode ```bash npm run dev ``` -Verify hot reload works and no runtime errors. - -### 5. Test in Production Mode (if applicable) - -```bash -npm run start:prodtests -``` - -Verify production build works correctly. - -## Critical Documentation Rule - -From CLAUDE.md: +## Documentation Updates -> **Si durante una tarea modificas la estructura de carpetas, añades un nuevo servicio/app/provider, o cambias una decisión arquitectónica clave, DEBES actualizar el archivo `CLAUDE.md` y/o los archivos en `.claude/parts/` como parte de tus cambios.** +When modifying a module: +- Update its `README.md` if behavior changes (max 15 lines) +- `config.json` documents dependencies - update if needed -## What to Update When +When adding new modules: +- Create `README.md` in the module directory (ultra-brief) -- New app/service/provider/utility → Update relevant `.claude/parts/*.md` -- Architecture changes → Update `ARCHITECTURE.md` -- New commands → Update `CLAUDE.md` Commands table -- New concepts → Update `CLAUDE.md` Key Concepts table +Architecture changes: +- Update Serena memories for internal details +- Update `CLAUDE.md` only for fundamental concepts diff --git a/.serena/memories/uifederation_architecture.md b/.serena/memories/uifederation_architecture.md new file mode 100644 index 00000000..7b2db4cd --- /dev/null +++ b/.serena/memories/uifederation_architecture.md @@ -0,0 +1,97 @@ +# UIFederationService Architecture + +## Core Components + +### 1. Module Loading (kernel.ts) +- `#buildAppLoadQueue()`: Orders apps by dependencies using `uiDependencies` from config.json +- UI libraries (Stencil) are detected by `framework === "stencil" && exports` and loaded first +- Apps wait for their dependencies before being added to the queue +- **Current Issue**: Loading is sequential, not parallel + +### 2. UIFederationService (index.ts) +- Manages UI module registration, building, and serving +- Uses namespace-based organization: `Map>` +- Supports both development (Express) and production (Fastify with host-based routing) +- Key methods: + - `registerUIModule()`: Registers and builds a UI module + - `#waitForUILibraryBuild()`: Waits for Stencil UI library before building other modules + - `#regenerateLayoutConfigsForNamespace()`: Restarts host dev servers when new remotes register + +### 3. Build Strategies +- **Stencil (stencil-strategy.ts)**: Web components, outputs loader + custom-elements +- **Rspack (rspack/base.ts)**: React/Vue with Module Federation +- **Vite (vite/base.ts)**: React/Vue with import maps + +### 4. Module Federation (Rspack) +- `detectRemotes()`: Scans registered modules for remotes (non-layout, with devPort, same namespace) +- Hosts get `remotes: {...}` config pointing to remote mf-manifest.json URLs +- Remotes expose `./App` component + +## Key Concepts + +### uiDependencies +Array in config.json that declares which modules must load before this one: +```json +{ + "uiModule": { + "uiDependencies": ["home", "web-ui-library"] + } +} +``` + +### Namespace Isolation +Modules in different namespaces are isolated: +- `uiNamespace: "default"` (implicit if not set) +- `uiNamespace: "adc-platform"` (explicit) + +### Host vs Remote +- `isHost: true`: Main app that loads remotes +- `isRemote: true`: Module exposed via Module Federation + +## Auto-Init System (2025-12) + +### How it works +1. `stencil-strategy.ts` generates `init.js` + `styles.css` in output dir +2. `init.js` auto-executes `defineCustomElements(window)` on import +3. `alias-generator.ts` maps: + - `@ui-library` → `init.js` (auto-registers components) + - `@ui-library/styles` → `styles.css` (CSS base) + - `@ui-library/utils/*` → utilities from source + +### Usage in apps +```typescript +import "@ui-library"; // Auto-registers Web Components +import "@ui-library/styles"; // Loads CSS base +``` + +## Tailwind CSS Architecture (2025-12) + +### Independent CSS Compilation +Each module compiles its own Tailwind CSS independently: +- **Hosts/Layouts**: Only scan their own `src/` directory + UI Library paths +- **Remotes**: Compile their own CSS with Tailwind postprocessing +- **UI Libraries**: Included in all modules' Tailwind scan (shared components) + +### How it works +1. `generateTailwindConfig()` generates a CSS entry file with `@source` directives +2. Each module only scans its own source files (no cross-module scanning for remotes) +3. PostCSS processes the CSS with Tailwind v4 and autoprefixer +4. Rspack bundles the processed CSS using `style-loader` (injects CSS in DOM) +5. Module Federation loads remotes with their CSS automatically + +### Benefits +- **Decoupling**: Modifying/adding a remote doesn't require host recompilation +- **Smaller bundles**: Each module only includes CSS for classes it actually uses +- **Scalability**: Supports hundreds of microfrontends without bloating the host CSS +- **Duplication trade-off**: Accepts 3-4 CSS duplications over massive unused CSS + +## Load Order +1. **Level 0**: UI Libraries (Stencil) - parallel +2. **Level 1+**: Apps by dependency level - parallel within level +3. **Last Level**: Hosts - after all remotes + +## Documentation Pattern +- Each module has its own `README.md` (max 15 lines) +- `config.json` documents dependencies +- NO centralized redundant docs +- Serena memories for architecture internals \ No newline at end of file diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 53308546..95c3f915 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -293,7 +293,7 @@ El proyecto incluye una librería de componentes UI (`00-web-ui-library`) constr ```typescript // En cualquier app React/Vue/etc: -import '@ui-library/loader'; +import "@ui-library"; // Uso en JSX/TSX: diff --git a/CLAUDE.md b/CLAUDE.md index 4bc33aab..e6be18fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,123 +1,42 @@ -# ADC Platform - Context Map +# ADC Platform -## Project Overview +Kernel modular con carga dinámica de apps, services, providers y utilities. +Utilizar patrones KISS, DRY, SOLID y YAGNI. -Kernel modular que carga dinámicamente apps, services, providers y utilities. Soporta versionado semántico, hot reload, y múltiples lenguajes (TS/Python via IPC). - -## Structure Map +## Estructura ``` src/ -├── index.ts # Entry point -├── kernel.ts # Orquestador central (~38K líneas) -├── apps/ -│ ├── BaseApp.ts # Clase base para apps -│ ├── community/ # Apps de la comunidad -│ ├── core/ # Apps del núcleo -│ ├── private/ # Apps privadas -│ └── test/ # Apps de desarrollo/testing -│ ├── 00-web-ui-library/ # UI Components (Stencil) -│ ├── 00-web-ui-library-mobile/ -│ ├── web-layout/ # Layout principal (React) -│ ├── web-layout-mobile/ -│ ├── web-home/ # Home page -│ ├── web-home-mobile/ -│ ├── web-config/ # Configuración UI -│ ├── users-management/ # Gestión usuarios -│ ├── user-profile-file/ # Profile (file storage) -│ └── user-profile-mongo/ # Profile (MongoDB) -├── services/ -│ ├── BaseService.ts -│ ├── core/ # Servicios modo kernel -│ │ ├── ExecutionManagerService/ # Workers/distribución -│ │ ├── IdentityManagerService/ # Auth/roles -│ │ ├── LangManagerService/ # i18n -│ │ ├── LogManagerService/ # Logging -│ │ └── UIFederationService/ # Module Federation -│ └── data/ # Servicios de datos -├── providers/ -│ ├── BaseProvider.ts -│ ├── files/ # Filesystem -│ ├── http/ # HTTP servers -│ │ ├── express-server/ # Express (desarrollo) -│ │ └── fastify-server/ # Fastify + host routing (producción) -│ └── object/ # Object storage -├── utilities/ -│ ├── BaseUtility.ts -│ └── adapters/ # Adaptadores -├── utils/ # Helpers internos -│ ├── decorators/ -│ ├── ipc/ # Inter-process communication -│ ├── loaders/ # Module loaders -│ ├── logger/ -│ └── react/ -└── interfaces/ # TypeScript interfaces - ├── behaviours/ - ├── interop/ - ├── modules/ - └── utils/ +├── kernel.ts # Orquestador central +├── apps/ # Aplicaciones (cada una con README.md) +│ ├── public/ # Apps públicas (adc-platform namespace) +│ └── test/ # Apps de desarrollo (default namespace) +├── services/core/ # Servicios kernel (cada uno con README.md) +├── providers/ # Proveedores (cada uno con README.md) +└── utils/ # Helpers internos ``` ## Commands -| Command | Description | -| ------------------------- | -------------------------------------- | -| `npm run start` | Producción (sin tests) | -| `npm dev` | Dev mode (hot reload) | +| Command | Description | +| ----------------- | ------------------------------ | +| `npm run dev` | Desarrollo (hot reload) | | `npm run start:prodtests` | Simular producción + tests habilitados | -| `npm run typecheck` | TypeScript + ts-prune | -| `npm run lint` | ESLint | -| `npm run lint:fix` | ESLint autofix | +| `npm run start` | Producción (puerto 80) | +| `npm run typecheck` | TypeScript check | +| `npm run lint` | ESLint | | `npm run cleanup` | Limpiar procesos | -| `npm run create:app` | Scaffold nueva app | -| `npm run create:service` | Scaffold nuevo service | -| `npm run create:provider` | Scaffold nuevo provider | -| `npm run create:utility` | Scaffold nueva utility | - -## Workflow - Context Economy Protocol - -**ANTES de codificar cualquier módulo:** - -1. **NO leas todo el código al inicio.** Usa este mapa para orientarte. -2. **Busca documentación local primero:** - - `{module}/README.md` si existe - - `{module}/config.json` para dependencias - - `.claude/parts/*.md` para detalles específicos -3. **Solo lee el código que vas a modificar.** -4. **Para detalles de arquitectura global:** consulta `ARCHITECTURE.md` -**Referencias rápidas:** +## Key Concepts -- Services: `.claude/parts/services.md` -- Providers: `.claude/parts/providers.md` -- UI System: `.claude/parts/ui-system.md` - -## Key Concepts (Quick Reference) - -| Concepto | Descripción | -| ------------------ | ---------------------------------------------- | -| `kernelMode: true` | Service se carga con el kernel (global) | -| `config.json` | Declara dependencias de una app/service | -| `config-*.json` | Múltiples instancias de una app | +| Concepto | Descripción | +| -------- | ----------- | +| `config.json` | Dependencias y configuración del módulo | +| `uiDependencies` | Apps UI que deben cargarse antes | +| `@ui-library` | Auto-registra Web Components al importarse | +| `@ui-library/styles` | CSS base de la UI Library | +| `uiNamespace` | Aísla UI libraries (ej: `adc-platform`, `default`) | | `@Distributed` | Decorador para ejecutar en worker | -| Namespace UI | Múltiples UI libraries sin colisiones | -| Workspaces | Cada módulo es un npm package aislado | -| `hosting` | Config de dominios/subdominios para producción | - -## Production Hosting - -En producción, las apps UI se sirven mediante **virtual hosts** (dominios/subdominios). - -**Providers HTTP:** - -- `express-server`: usado en desarrollo (`npm run dev`) -- `fastify-server`: usado en producción con host-based routing - -**Puertos:** - -- `npm run start` → puerto 80 (producción real) -- `npm run start:prodtests` → puerto 3000 (tests de producción) -- `npm run dev` → puerto 3000 + dev servers en puertos separados **Configuración de hosting en `config.json`:** @@ -129,19 +48,19 @@ En producción, las apps UI se sirven mediante **virtual hosts** (dominios/subdo } } } -``` - -## Code Style -- **KISS, DRY, SOLID, YAGNI** -- Priorizar legibilidad sobre cleverness -- No documentar lo obvio -- TypeScript strict mode -- ESLint sin warnings (`--max-warnings 0`) -- Un `config.json` por módulo para dependencias +## UI Apps -## CRITICAL: Self-Maintenance Rule +```typescript +// main.tsx - Patrón de imports +import "@ui-library"; // Auto-registra Web Components +import "@ui-library/styles"; // CSS base (variables, tipografía, etc.) +import "./styles/tailwind.css"; // Extensiones locales (solo Tailwind + extensiones propias) +``` -> **Si durante una tarea modificas la estructura de carpetas, añades un nuevo servicio/app/provider, o cambias una decisión arquitectónica clave, DEBES actualizar este archivo `CLAUDE.md` y/o los archivos en `.claude/parts/` como parte de tus cambios.** +## Documentation Rules -Esta regla es **obligatoria** para mantener la economía de contexto. +- Cada módulo tiene su propio `README.md` (máx 15 líneas) +- `config.json` documenta dependencias +- NO crear documentación centralizada redundante ni documentar lo obvio +- Al modificar un módulo, actualizar SU readme si es necesario diff --git a/eslint.config.js b/eslint.config.js index f6f406ac..23afb255 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,6 +5,33 @@ import tseslint from 'typescript-eslint'; export default [ js.configs.recommended, ...tseslint.configs.recommended, + { + ignores: ['**/*.d.ts'], + }, + { + files: [ + 'src/apps/**/web-*/**/*.{js,jsx,tsx}' + ], + languageOptions: { + parser: tseslint.parser, + ecmaVersion: 'latest', + sourceType: 'module', + globals: { + window: 'readonly', + document: 'readonly', + localStorage: 'readonly', + navigator: 'readonly', + fetch: 'readonly', + console: 'readonly', + }, + }, + rules: { + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'no-console': 'off', + '@typescript-eslint/no-require-imports': 'error', // keep forbid require + }, + }, { languageOptions: { parser: tseslint.parser, @@ -21,6 +48,6 @@ export default [ rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': 'off', - } + }, }, ]; diff --git a/package-lock.json b/package-lock.json index e8c9593c..2ab686ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "@rspack/core": "^1.6.6", "@rspack/dev-server": "^1.1.4", "@stencil-community/postcss": "^2.2.0", - "@stencil/core": "^4.38.3", + "@stencil/core": "^4.39.0", "@tailwindcss/postcss": "^4.1.17", "@types/node": "^20.14.2", "@types/react": "^19.2.1", @@ -55,6 +55,10 @@ "vue-loader": "^17.4.2" } }, + "node_modules/@adc-platform/content-service": { + "resolved": "src/services/data/content-service", + "link": true + }, "node_modules/@adc-platform/execution-manager-service": { "resolved": "src/services/core/ExecutionManagerService", "link": true @@ -631,6 +635,12 @@ "node": ">=6.9.0" } }, + "node_modules/@bufbuild/protobuf": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.2.tgz", + "integrity": "sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@capsizecss/unpack": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-3.0.1.tgz", @@ -643,6 +653,44 @@ "node": ">=18" } }, + "node_modules/@connectrpc/connect": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.1.tgz", + "integrity": "sha512-JzhkaTvM73m2K1URT6tv53k2RwngSmCXLZJgK580qNQOXRzZRR/BCMfZw3h+90JpnG6XksP5bYT+cz0rpUzUWQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0" + } + }, + "node_modules/@connectrpc/connect-fastify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-fastify/-/connect-fastify-2.1.1.tgz", + "integrity": "sha512-UMkdus491H+iUTQE/wnZjaiX6oQ3+GH7gfE5kq2AtXZEZ4IV02bOmTYZS+jfu63+MMeyAkacgfruCYfvwqYzxA==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0", + "@connectrpc/connect": "2.1.1", + "@connectrpc/connect-node": "2.1.1", + "fastify": "^4.22.1 || ^5.1.0" + } + }, + "node_modules/@connectrpc/connect-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.1.1.tgz", + "integrity": "sha512-s3TfsI1XF+n+1z6MBS9rTnFsxxR4Rw5wmdEnkQINli81ESGxcsfaEet8duzq8LVuuCupmhUsgpRo0Nv9pZkufg==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0", + "@connectrpc/connect": "2.1.1" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -3454,9 +3502,10 @@ } }, "node_modules/@stencil/core": { - "version": "4.38.3", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.3.tgz", - "integrity": "sha512-rSDzGUfi58X8K79ySjlM6KlY+mq7D+ittzgNAdYHcsXHc70sBpdatFhnbOg25uVDiMf7xRAH9slP38pPdXnZOQ==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.39.0.tgz", + "integrity": "sha512-wLASFh5wecnbxY+9pEPd6bl7AZJksLmuiBd0ShvkJ0v/N1nL4HNSw/jq2+TzgFE1+XqCUhKPDeVXFpZf1uuRDw==", + "dev": true, "license": "MIT", "bin": { "stencil": "bin/stencil" @@ -3483,6 +3532,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3496,6 +3546,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3509,6 +3560,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3522,6 +3574,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3535,6 +3588,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3548,6 +3602,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3561,6 +3616,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3574,6 +3630,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15461,10 +15518,7 @@ }, "src/apps/test/00-web-ui-library-mobile": { "name": "@adc-platform/web-ui-library-mobile", - "version": "1.0.0", - "dependencies": { - "@stencil/core": "^4.27.0" - } + "version": "1.0.0" }, "src/apps/test/01-web-ui-library-mobile": { "name": "@adc-platform/web-ui-library-mobile", @@ -15571,6 +15625,8 @@ "name": "@adc-platform/fastify-server", "version": "1.0.0", "dependencies": { + "@connectrpc/connect": "^2.1.1", + "@connectrpc/connect-fastify": "^2.1.1", "@fastify/cors": "^11.1.0", "@fastify/formbody": "^8.0.2", "fastify": "^5.3.3" @@ -15605,6 +15661,14 @@ "name": "@adc-platform/ui-federation-service", "version": "1.0.0" }, + "src/services/data/content-service": { + "name": "@adc-platform/content-service", + "version": "1.0.0", + "dependencies": { + "@bufbuild/protobuf": "^2.2.2", + "@connectrpc/connect": "^2.1.1" + } + }, "src/services/data/json-file-crud": { "name": "@adc-platform/json-file-crud-service" }, diff --git a/package.json b/package.json index b43dd2eb..b4a80572 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,9 @@ "src/services/*/*" ], "scripts": { - "start": "cross-env NODE_ENV=production EXCLUDE_TESTS=true tsx src/index.ts", - "dev": "cross-env NODE_ENV=development tsx src/index.ts", - "start:prodtests": "cross-env NODE_ENV=production EXCLUDE_TESTS=false tsx src/index.ts", + "start": "cross-env NODE_ENV=production tsx src/index.ts", + "dev": "cross-env NODE_ENV=development ENABLE_TESTS=true tsx src/index.ts", + "start:prodtests": "cross-env NODE_ENV=production ENABLE_TESTS=true PROD_PORT=3000 tsx src/index.ts", "lint": "eslint src --ext .ts --max-warnings 0", "lint:fix": "eslint src --ext .ts --fix", "typecheck": "bash scripts/recursive-typecheck.sh && ts-prune", @@ -50,7 +50,7 @@ "@rspack/core": "^1.6.6", "@rspack/dev-server": "^1.1.4", "@stencil-community/postcss": "^2.2.0", - "@stencil/core": "^4.38.3", + "@stencil/core": "^4.39.0", "@tailwindcss/postcss": "^4.1.17", "@types/node": "^20.14.2", "@types/react": "^19.2.1", diff --git a/src/apps/BaseApp.ts b/src/apps/BaseApp.ts index cfa3aa0a..bc9ed0ec 100644 --- a/src/apps/BaseApp.ts +++ b/src/apps/BaseApp.ts @@ -6,7 +6,7 @@ import { ILogger } from "../interfaces/utils/ILogger.js"; import { Kernel } from "../kernel.js"; import { IModuleConfig } from "../interfaces/modules/IModule.js"; import type { UIModuleConfig } from "../interfaces/modules/IUIModule.js"; -import type { IUIFederationService } from "../services/core/UIFederationService/types.js"; +import UIFederationService from "../services/core/UIFederationService/index.ts"; /** * Clase base abstracta para todas las Apps. @@ -56,9 +56,8 @@ export abstract class BaseApp implements IApp { // Desregistrar módulo UI si estaba registrado if (this.uiModuleRegistered && this.config?.uiModule) { try { - const uiFederation = this.kernel.getService("UIFederationService"); - const uiFederationInstance = (await uiFederation.getInstance()) as IUIFederationService; - await uiFederationInstance.unregisterUIModule(this.config.uiModule.name); + const uiFederation = this.kernel.getService("UIFederationService"); + await uiFederation.unregisterUIModule(this.config.uiModule.name); } catch (err) { this.logger.logDebug("No se pudo desregistrar módulo UI", err); } @@ -69,6 +68,26 @@ export abstract class BaseApp implements IApp { * Combina la configuración de `default.json` (base) con la configuración * de la instancia específica de la app. */ + /** + * Carga el archivo .env de la app si existe + */ + async #loadAppEnv(): Promise { + try { + const { config } = await import("dotenv"); + const envPath = path.join(this.appDir, ".env"); + + try { + await fs.access(envPath); + config({ path: envPath }); + this.logger.logDebug(`Variables de entorno cargadas desde ${envPath}`); + } catch { + // No hay archivo .env, lo cual es aceptable + } + } catch (error: any) { + this.logger.logWarn(`Error cargando .env: ${error.message}`); + } + } + async #mergeModuleConfigs(): Promise { const appDir = this.appDir; @@ -124,6 +143,9 @@ export abstract class BaseApp implements IApp { */ public async loadModulesFromConfig(): Promise { try { + // Cargar variables de entorno de la app primero + await this.#loadAppEnv(); + await this.#mergeModuleConfigs(); if (this.config) { await Kernel.moduleLoader.loadAllModulesFromDefinition(this.config, this.kernel); @@ -143,8 +165,7 @@ export abstract class BaseApp implements IApp { } try { - const uiFederation = this.kernel.getService("UIFederationService"); - const uiFederationInstance = (await uiFederation.getInstance()) as IUIFederationService; + const uiFederationService = this.kernel.getService("UIFederationService"); const uiConfig: UIModuleConfig = this.config.uiModule; @@ -157,7 +178,7 @@ export abstract class BaseApp implements IApp { uiConfig.name = cleanModuleName; this.logger.logInfo(`Registrando módulo UI: ${cleanModuleName}`); - await uiFederationInstance.registerUIModule(cleanModuleName, this.appDir, uiConfig); + await uiFederationService.registerUIModule(cleanModuleName, this.appDir, uiConfig); this.uiModuleRegistered = true; this.logger.logOk(`Módulo UI ${cleanModuleName} registrado exitosamente`); diff --git a/src/apps/public/00-adc-ui-library/README.md b/src/apps/public/00-adc-ui-library/README.md new file mode 100644 index 00000000..1f0cf971 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/README.md @@ -0,0 +1,15 @@ +# ADC UI Library + +Web Components (Stencil) para el namespace `adc-platform`. + +## Uso +```typescript +import "@ui-library"; // Auto-registra componentes +import "@ui-library/styles"; // CSS base (variables, tipografía) +``` + +## Componentes +``, ``, ``, ``, etc. + +## CSS Variables +Definidas en `src/global/tailwind.css`: `--c-primary`, `--c-accent`, `--c-text`, etc. diff --git a/src/apps/public/00-adc-ui-library/config.json b/src/apps/public/00-adc-ui-library/config.json new file mode 100644 index 00000000..3f72cc0d --- /dev/null +++ b/src/apps/public/00-adc-ui-library/config.json @@ -0,0 +1,13 @@ +{ + "uiModule": { + "name": "adc-ui-library", + "uiNamespace": "adc-platform", + "framework": "stencil", + "outputDir": "dist-ui", + "exports": { + "loader": "./loader", + "utils": "./utils", + "tailwind-preset": "./utils/tailwind-preset.js" + } + } +} diff --git a/src/apps/public/00-adc-ui-library/index.ts b/src/apps/public/00-adc-ui-library/index.ts new file mode 100644 index 00000000..86b435c1 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/index.ts @@ -0,0 +1,8 @@ +import { BaseApp } from "../../BaseApp.ts"; + +export default class AdcUILibraryApp extends BaseApp { + async run(): Promise { + this.logger.logInfo(`${this.name} - ADC UI Library disponible`); + this.logger.logOk(`${this.name} - Componentes Stencil listos para ser consumidos`); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components.d.ts b/src/apps/public/00-adc-ui-library/src/components.d.ts new file mode 100644 index 00000000..6f0cea76 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components.d.ts @@ -0,0 +1,1220 @@ +/* eslint-disable */ +/* tslint:disable */ +/** + * This is an autogenerated file created by the Stencil compiler. + * It contains typing information for all components that exist in this project. + */ +import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; +import { Block } from "./components/organisms/adc-blocks-renderer/adc-blocks-renderer"; +import { DropdownMenuItem } from "./components/molecules/adc-dropdown-menu/adc-dropdown-menu"; +import { InlineToken } from "./components/atoms/adc-inline-tokens/adc-inline-tokens"; +import { SelectOption } from "./components/atoms/adc-select/adc-select"; +export { Block } from "./components/organisms/adc-blocks-renderer/adc-blocks-renderer"; +export { DropdownMenuItem } from "./components/molecules/adc-dropdown-menu/adc-dropdown-menu"; +export { InlineToken } from "./components/atoms/adc-inline-tokens/adc-inline-tokens"; +export { SelectOption } from "./components/atoms/adc-select/adc-select"; +export namespace Components { + interface AdcBlocksRenderer { + /** + * @default [] + */ + "blocks": Block[]; + } + interface AdcButton { + "ariaLabel"?: string; + "href"?: string; + /** + * @default "button" + */ + "type": "button" | "submit" | "reset"; + } + interface AdcButtonRounded { + "ariaLabel"?: string; + "href"?: string; + /** + * @default "button" + */ + "type": "button" | "submit" | "reset"; + } + interface AdcCallout { + /** + * @default "note" + */ + "role": "note" | "status" | "alert"; + /** + * @default "info" + */ + "tone": "info" | "warning" | "success" | "error"; + } + interface AdcCodeBlock { + "ariaLabel"?: string; + /** + * @default "" + */ + "content": string; + "language"?: string; + } + interface AdcContentCard { + "bannerAlt"?: string; + "bannerUrl"?: string; + /** + * @default false + */ + "compact": boolean; + "description"?: string; + "href"?: string; + /** + * @default "" + */ + "title": string; + } + interface AdcDivider { + } + interface AdcDropdownMenu { + /** + * @default "left" + */ + "alignState": "left" | "right"; + /** + * @default [] + */ + "items": DropdownMenuItem[]; + /** + * @default true + */ + "openOnHover": boolean; + } + interface AdcFeatureCard { + /** + * @default true + */ + "staticRender": boolean; + /** + * @default "" + */ + "title": string; + } + interface AdcIconCommunity { + /** + * @default "2rem" + */ + "size": string; + } + interface AdcIconLearning { + /** + * @default "2rem" + */ + "size": string; + } + interface AdcIconLeftArrow { + /** + * @default "1rem" + */ + "size": string; + } + interface AdcIconLogout { + /** + * @default "1rem" + */ + "size": string; + } + interface AdcIconNitro { + /** + * @default "1rem" + */ + "size": string; + } + interface AdcIconOpensource { + /** + * @default "2rem" + */ + "size": string; + } + interface AdcIconPencil { + /** + * @default "1rem" + */ + "size": string; + } + interface AdcIconVip { + /** + * @default "1rem" + */ + "size": string; + } + interface AdcInlineTokens { + /** + * @default "" + */ + "fallback": string; + /** + * @default [] + */ + "tokens": InlineToken[]; + } + interface AdcInput { + /** + * @default "" + */ + "ariaLabel"?: string; + /** + * @default "off" + */ + "autocomplete"?: string; + /** + * @default "" + */ + "inputId"?: string; + /** + * @default "" + */ + "name"?: string; + /** + * @default "" + */ + "placeholder"?: string; + /** + * @default "text" + */ + "type"?: string; + /** + * @default "" + */ + "value": string; + } + interface AdcListBlock { + "ariaLabel"?: string; + /** + * @default [] + */ + "items": string[]; + /** + * @default false + */ + "ordered": boolean; + "start"?: number; + } + interface AdcLpBadge { + "as"?: "button" | "span"; + /** + * @default "" + */ + "color": string; + "slug"?: string; + /** + * @default "" + */ + "title": string; + } + interface AdcQuote { + /** + * @default true + */ + "staticRender": boolean; + } + interface AdcSearchInput { + /** + * @default "" + */ + "ariaLabel"?: string; + /** + * @default "off" + */ + "autocomplete"?: string; + /** + * @default 300 + */ + "debounce": number; + /** + * @default "" + */ + "inputId"?: string; + /** + * @default "" + */ + "name"?: string; + /** + * @default "" + */ + "placeholder": string; + /** + * @default "text" + */ + "type"?: string; + /** + * @default "" + */ + "value": string; + } + interface AdcSelect { + /** + * @default [] + */ + "options": SelectOption[]; + /** + * @default "Seleccione" + */ + "placeholder": string; + /** + * @default "" + */ + "value": string; + } + interface AdcShareButtons { + /** + * @default "" + */ + "description": string; + /** + * @default "" + */ + "title": string; + /** + * @default "" + */ + "url": string; + } + interface AdcSiteFooter { + /** + * @default "" + */ + "brandName": string; + /** + * @default "" + */ + "brandSlogan": string; + /** + * @default "" + */ + "creatorHref": string; + /** + * @default "" + */ + "creatorName": string; + /** + * @default false + */ + "lowerSign": boolean; + /** + * @default false + */ + "registered": boolean; + } + interface AdcSiteHeader { + /** + * @default "/" + */ + "homeHref": string; + /** + * @default "" + */ + "logoAlt": string; + /** + * @default "" + */ + "logoSrc": string; + } + interface AdcStarRating { + "average"?: number | null; + /** + * @default false + */ + "canRate": boolean; + "count"?: number | null; + "myRating"?: number | null; + /** + * @default false + */ + "pending": boolean; + } + interface AdcTableBlock { + "caption"?: string; + "columnAlign"?: Array<"left" | "center" | "right">; + /** + * @default [] + */ + "header": string[]; + /** + * @default false + */ + "rowHeaders": boolean; + /** + * @default [] + */ + "rows": string[][]; + } + interface AdcTestimonialCard { + /** + * @default "" + */ + "author": string; + /** + * @default true + */ + "staticRender": boolean; + } + interface AdcText { + /** + * @default true + */ + "contain": boolean; + /** + * @default true + */ + "staticRender": boolean; + } + /** + * Componente YouTube Facade para carga perezosa de videos + * Muestra una thumbnail clickeable en lugar de cargar el iframe inmediatamente + * Mejora performance al evitar cargar el player de YouTube hasta que sea necesario + */ + interface AdcYoutubeFacade { + /** + * Alto del contenedor (opcional, por defecto responsive 16:9) + */ + "height"?: string; + /** + * Título del video para accesibilidad + * @default "Video de YouTube" + */ + "title": string; + /** + * ID del video de YouTube (extraído de la URL) + */ + "videoId": string; + /** + * Ancho del contenedor (opcional, por defecto responsive) + */ + "width"?: string; + } +} +export interface AdcButtonCustomEvent extends CustomEvent { + detail: T; + target: HTMLAdcButtonElement; +} +export interface AdcButtonRoundedCustomEvent extends CustomEvent { + detail: T; + target: HTMLAdcButtonRoundedElement; +} +export interface AdcContentCardCustomEvent extends CustomEvent { + detail: T; + target: HTMLAdcContentCardElement; +} +export interface AdcDropdownMenuCustomEvent extends CustomEvent { + detail: T; + target: HTMLAdcDropdownMenuElement; +} +export interface AdcInputCustomEvent extends CustomEvent { + detail: T; + target: HTMLAdcInputElement; +} +export interface AdcLpBadgeCustomEvent extends CustomEvent { + detail: T; + target: HTMLAdcLpBadgeElement; +} +export interface AdcSearchInputCustomEvent extends CustomEvent { + detail: T; + target: HTMLAdcSearchInputElement; +} +export interface AdcSelectCustomEvent extends CustomEvent { + detail: T; + target: HTMLAdcSelectElement; +} +export interface AdcStarRatingCustomEvent extends CustomEvent { + detail: T; + target: HTMLAdcStarRatingElement; +} +declare global { + interface HTMLAdcBlocksRendererElement extends Components.AdcBlocksRenderer, HTMLStencilElement { + } + var HTMLAdcBlocksRendererElement: { + prototype: HTMLAdcBlocksRendererElement; + new (): HTMLAdcBlocksRendererElement; + }; + interface HTMLAdcButtonElementEventMap { + "adcClick": MouseEvent; + } + interface HTMLAdcButtonElement extends Components.AdcButton, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLAdcButtonElement, ev: AdcButtonCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLAdcButtonElement, ev: AdcButtonCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLAdcButtonElement: { + prototype: HTMLAdcButtonElement; + new (): HTMLAdcButtonElement; + }; + interface HTMLAdcButtonRoundedElementEventMap { + "adcClick": MouseEvent; + } + interface HTMLAdcButtonRoundedElement extends Components.AdcButtonRounded, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLAdcButtonRoundedElement, ev: AdcButtonRoundedCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLAdcButtonRoundedElement, ev: AdcButtonRoundedCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLAdcButtonRoundedElement: { + prototype: HTMLAdcButtonRoundedElement; + new (): HTMLAdcButtonRoundedElement; + }; + interface HTMLAdcCalloutElement extends Components.AdcCallout, HTMLStencilElement { + } + var HTMLAdcCalloutElement: { + prototype: HTMLAdcCalloutElement; + new (): HTMLAdcCalloutElement; + }; + interface HTMLAdcCodeBlockElement extends Components.AdcCodeBlock, HTMLStencilElement { + } + var HTMLAdcCodeBlockElement: { + prototype: HTMLAdcCodeBlockElement; + new (): HTMLAdcCodeBlockElement; + }; + interface HTMLAdcContentCardElementEventMap { + "cardClick": MouseEvent; + } + interface HTMLAdcContentCardElement extends Components.AdcContentCard, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLAdcContentCardElement, ev: AdcContentCardCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLAdcContentCardElement, ev: AdcContentCardCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLAdcContentCardElement: { + prototype: HTMLAdcContentCardElement; + new (): HTMLAdcContentCardElement; + }; + interface HTMLAdcDividerElement extends Components.AdcDivider, HTMLStencilElement { + } + var HTMLAdcDividerElement: { + prototype: HTMLAdcDividerElement; + new (): HTMLAdcDividerElement; + }; + interface HTMLAdcDropdownMenuElementEventMap { + "adcItemClick": DropdownMenuItem; + } + interface HTMLAdcDropdownMenuElement extends Components.AdcDropdownMenu, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLAdcDropdownMenuElement, ev: AdcDropdownMenuCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLAdcDropdownMenuElement, ev: AdcDropdownMenuCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLAdcDropdownMenuElement: { + prototype: HTMLAdcDropdownMenuElement; + new (): HTMLAdcDropdownMenuElement; + }; + interface HTMLAdcFeatureCardElement extends Components.AdcFeatureCard, HTMLStencilElement { + } + var HTMLAdcFeatureCardElement: { + prototype: HTMLAdcFeatureCardElement; + new (): HTMLAdcFeatureCardElement; + }; + interface HTMLAdcIconCommunityElement extends Components.AdcIconCommunity, HTMLStencilElement { + } + var HTMLAdcIconCommunityElement: { + prototype: HTMLAdcIconCommunityElement; + new (): HTMLAdcIconCommunityElement; + }; + interface HTMLAdcIconLearningElement extends Components.AdcIconLearning, HTMLStencilElement { + } + var HTMLAdcIconLearningElement: { + prototype: HTMLAdcIconLearningElement; + new (): HTMLAdcIconLearningElement; + }; + interface HTMLAdcIconLeftArrowElement extends Components.AdcIconLeftArrow, HTMLStencilElement { + } + var HTMLAdcIconLeftArrowElement: { + prototype: HTMLAdcIconLeftArrowElement; + new (): HTMLAdcIconLeftArrowElement; + }; + interface HTMLAdcIconLogoutElement extends Components.AdcIconLogout, HTMLStencilElement { + } + var HTMLAdcIconLogoutElement: { + prototype: HTMLAdcIconLogoutElement; + new (): HTMLAdcIconLogoutElement; + }; + interface HTMLAdcIconNitroElement extends Components.AdcIconNitro, HTMLStencilElement { + } + var HTMLAdcIconNitroElement: { + prototype: HTMLAdcIconNitroElement; + new (): HTMLAdcIconNitroElement; + }; + interface HTMLAdcIconOpensourceElement extends Components.AdcIconOpensource, HTMLStencilElement { + } + var HTMLAdcIconOpensourceElement: { + prototype: HTMLAdcIconOpensourceElement; + new (): HTMLAdcIconOpensourceElement; + }; + interface HTMLAdcIconPencilElement extends Components.AdcIconPencil, HTMLStencilElement { + } + var HTMLAdcIconPencilElement: { + prototype: HTMLAdcIconPencilElement; + new (): HTMLAdcIconPencilElement; + }; + interface HTMLAdcIconVipElement extends Components.AdcIconVip, HTMLStencilElement { + } + var HTMLAdcIconVipElement: { + prototype: HTMLAdcIconVipElement; + new (): HTMLAdcIconVipElement; + }; + interface HTMLAdcInlineTokensElement extends Components.AdcInlineTokens, HTMLStencilElement { + } + var HTMLAdcInlineTokensElement: { + prototype: HTMLAdcInlineTokensElement; + new (): HTMLAdcInlineTokensElement; + }; + interface HTMLAdcInputElementEventMap { + "adcInput": string; + } + interface HTMLAdcInputElement extends Components.AdcInput, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLAdcInputElement, ev: AdcInputCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLAdcInputElement, ev: AdcInputCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLAdcInputElement: { + prototype: HTMLAdcInputElement; + new (): HTMLAdcInputElement; + }; + interface HTMLAdcListBlockElement extends Components.AdcListBlock, HTMLStencilElement { + } + var HTMLAdcListBlockElement: { + prototype: HTMLAdcListBlockElement; + new (): HTMLAdcListBlockElement; + }; + interface HTMLAdcLpBadgeElementEventMap { + "adcClick": void; + } + interface HTMLAdcLpBadgeElement extends Components.AdcLpBadge, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLAdcLpBadgeElement, ev: AdcLpBadgeCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLAdcLpBadgeElement, ev: AdcLpBadgeCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLAdcLpBadgeElement: { + prototype: HTMLAdcLpBadgeElement; + new (): HTMLAdcLpBadgeElement; + }; + interface HTMLAdcQuoteElement extends Components.AdcQuote, HTMLStencilElement { + } + var HTMLAdcQuoteElement: { + prototype: HTMLAdcQuoteElement; + new (): HTMLAdcQuoteElement; + }; + interface HTMLAdcSearchInputElementEventMap { + "adcInput": string; + } + interface HTMLAdcSearchInputElement extends Components.AdcSearchInput, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLAdcSearchInputElement, ev: AdcSearchInputCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLAdcSearchInputElement, ev: AdcSearchInputCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLAdcSearchInputElement: { + prototype: HTMLAdcSearchInputElement; + new (): HTMLAdcSearchInputElement; + }; + interface HTMLAdcSelectElementEventMap { + "adcChange": string; + } + interface HTMLAdcSelectElement extends Components.AdcSelect, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLAdcSelectElement, ev: AdcSelectCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLAdcSelectElement, ev: AdcSelectCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLAdcSelectElement: { + prototype: HTMLAdcSelectElement; + new (): HTMLAdcSelectElement; + }; + interface HTMLAdcShareButtonsElement extends Components.AdcShareButtons, HTMLStencilElement { + } + var HTMLAdcShareButtonsElement: { + prototype: HTMLAdcShareButtonsElement; + new (): HTMLAdcShareButtonsElement; + }; + interface HTMLAdcSiteFooterElement extends Components.AdcSiteFooter, HTMLStencilElement { + } + var HTMLAdcSiteFooterElement: { + prototype: HTMLAdcSiteFooterElement; + new (): HTMLAdcSiteFooterElement; + }; + interface HTMLAdcSiteHeaderElement extends Components.AdcSiteHeader, HTMLStencilElement { + } + var HTMLAdcSiteHeaderElement: { + prototype: HTMLAdcSiteHeaderElement; + new (): HTMLAdcSiteHeaderElement; + }; + interface HTMLAdcStarRatingElementEventMap { + "adcRate": number; + } + interface HTMLAdcStarRatingElement extends Components.AdcStarRating, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLAdcStarRatingElement, ev: AdcStarRatingCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLAdcStarRatingElement, ev: AdcStarRatingCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLAdcStarRatingElement: { + prototype: HTMLAdcStarRatingElement; + new (): HTMLAdcStarRatingElement; + }; + interface HTMLAdcTableBlockElement extends Components.AdcTableBlock, HTMLStencilElement { + } + var HTMLAdcTableBlockElement: { + prototype: HTMLAdcTableBlockElement; + new (): HTMLAdcTableBlockElement; + }; + interface HTMLAdcTestimonialCardElement extends Components.AdcTestimonialCard, HTMLStencilElement { + } + var HTMLAdcTestimonialCardElement: { + prototype: HTMLAdcTestimonialCardElement; + new (): HTMLAdcTestimonialCardElement; + }; + interface HTMLAdcTextElement extends Components.AdcText, HTMLStencilElement { + } + var HTMLAdcTextElement: { + prototype: HTMLAdcTextElement; + new (): HTMLAdcTextElement; + }; + /** + * Componente YouTube Facade para carga perezosa de videos + * Muestra una thumbnail clickeable en lugar de cargar el iframe inmediatamente + * Mejora performance al evitar cargar el player de YouTube hasta que sea necesario + */ + interface HTMLAdcYoutubeFacadeElement extends Components.AdcYoutubeFacade, HTMLStencilElement { + } + var HTMLAdcYoutubeFacadeElement: { + prototype: HTMLAdcYoutubeFacadeElement; + new (): HTMLAdcYoutubeFacadeElement; + }; + interface HTMLElementTagNameMap { + "adc-blocks-renderer": HTMLAdcBlocksRendererElement; + "adc-button": HTMLAdcButtonElement; + "adc-button-rounded": HTMLAdcButtonRoundedElement; + "adc-callout": HTMLAdcCalloutElement; + "adc-code-block": HTMLAdcCodeBlockElement; + "adc-content-card": HTMLAdcContentCardElement; + "adc-divider": HTMLAdcDividerElement; + "adc-dropdown-menu": HTMLAdcDropdownMenuElement; + "adc-feature-card": HTMLAdcFeatureCardElement; + "adc-icon-community": HTMLAdcIconCommunityElement; + "adc-icon-learning": HTMLAdcIconLearningElement; + "adc-icon-left-arrow": HTMLAdcIconLeftArrowElement; + "adc-icon-logout": HTMLAdcIconLogoutElement; + "adc-icon-nitro": HTMLAdcIconNitroElement; + "adc-icon-opensource": HTMLAdcIconOpensourceElement; + "adc-icon-pencil": HTMLAdcIconPencilElement; + "adc-icon-vip": HTMLAdcIconVipElement; + "adc-inline-tokens": HTMLAdcInlineTokensElement; + "adc-input": HTMLAdcInputElement; + "adc-list-block": HTMLAdcListBlockElement; + "adc-lp-badge": HTMLAdcLpBadgeElement; + "adc-quote": HTMLAdcQuoteElement; + "adc-search-input": HTMLAdcSearchInputElement; + "adc-select": HTMLAdcSelectElement; + "adc-share-buttons": HTMLAdcShareButtonsElement; + "adc-site-footer": HTMLAdcSiteFooterElement; + "adc-site-header": HTMLAdcSiteHeaderElement; + "adc-star-rating": HTMLAdcStarRatingElement; + "adc-table-block": HTMLAdcTableBlockElement; + "adc-testimonial-card": HTMLAdcTestimonialCardElement; + "adc-text": HTMLAdcTextElement; + "adc-youtube-facade": HTMLAdcYoutubeFacadeElement; + } +} +declare namespace LocalJSX { + interface AdcBlocksRenderer { + /** + * @default [] + */ + "blocks"?: Block[]; + } + interface AdcButton { + "ariaLabel"?: string; + "href"?: string; + "onAdcClick"?: (event: AdcButtonCustomEvent) => void; + /** + * @default "button" + */ + "type"?: "button" | "submit" | "reset"; + } + interface AdcButtonRounded { + "ariaLabel"?: string; + "href"?: string; + "onAdcClick"?: (event: AdcButtonRoundedCustomEvent) => void; + /** + * @default "button" + */ + "type"?: "button" | "submit" | "reset"; + } + interface AdcCallout { + /** + * @default "note" + */ + "role"?: "note" | "status" | "alert"; + /** + * @default "info" + */ + "tone"?: "info" | "warning" | "success" | "error"; + } + interface AdcCodeBlock { + "ariaLabel"?: string; + /** + * @default "" + */ + "content"?: string; + "language"?: string; + } + interface AdcContentCard { + "bannerAlt"?: string; + "bannerUrl"?: string; + /** + * @default false + */ + "compact"?: boolean; + "description"?: string; + "href"?: string; + "onCardClick"?: (event: AdcContentCardCustomEvent) => void; + /** + * @default "" + */ + "title"?: string; + } + interface AdcDivider { + } + interface AdcDropdownMenu { + /** + * @default "left" + */ + "alignState"?: "left" | "right"; + /** + * @default [] + */ + "items"?: DropdownMenuItem[]; + "onAdcItemClick"?: (event: AdcDropdownMenuCustomEvent) => void; + /** + * @default true + */ + "openOnHover"?: boolean; + } + interface AdcFeatureCard { + /** + * @default true + */ + "staticRender"?: boolean; + /** + * @default "" + */ + "title"?: string; + } + interface AdcIconCommunity { + /** + * @default "2rem" + */ + "size"?: string; + } + interface AdcIconLearning { + /** + * @default "2rem" + */ + "size"?: string; + } + interface AdcIconLeftArrow { + /** + * @default "1rem" + */ + "size"?: string; + } + interface AdcIconLogout { + /** + * @default "1rem" + */ + "size"?: string; + } + interface AdcIconNitro { + /** + * @default "1rem" + */ + "size"?: string; + } + interface AdcIconOpensource { + /** + * @default "2rem" + */ + "size"?: string; + } + interface AdcIconPencil { + /** + * @default "1rem" + */ + "size"?: string; + } + interface AdcIconVip { + /** + * @default "1rem" + */ + "size"?: string; + } + interface AdcInlineTokens { + /** + * @default "" + */ + "fallback"?: string; + /** + * @default [] + */ + "tokens"?: InlineToken[]; + } + interface AdcInput { + /** + * @default "" + */ + "ariaLabel"?: string; + /** + * @default "off" + */ + "autocomplete"?: string; + /** + * @default "" + */ + "inputId"?: string; + /** + * @default "" + */ + "name"?: string; + "onAdcInput"?: (event: AdcInputCustomEvent) => void; + /** + * @default "" + */ + "placeholder"?: string; + /** + * @default "text" + */ + "type"?: string; + /** + * @default "" + */ + "value"?: string; + } + interface AdcListBlock { + "ariaLabel"?: string; + /** + * @default [] + */ + "items"?: string[]; + /** + * @default false + */ + "ordered"?: boolean; + "start"?: number; + } + interface AdcLpBadge { + "as"?: "button" | "span"; + /** + * @default "" + */ + "color"?: string; + "onAdcClick"?: (event: AdcLpBadgeCustomEvent) => void; + "slug"?: string; + /** + * @default "" + */ + "title"?: string; + } + interface AdcQuote { + /** + * @default true + */ + "staticRender"?: boolean; + } + interface AdcSearchInput { + /** + * @default "" + */ + "ariaLabel"?: string; + /** + * @default "off" + */ + "autocomplete"?: string; + /** + * @default 300 + */ + "debounce"?: number; + /** + * @default "" + */ + "inputId"?: string; + /** + * @default "" + */ + "name"?: string; + "onAdcInput"?: (event: AdcSearchInputCustomEvent) => void; + /** + * @default "" + */ + "placeholder"?: string; + /** + * @default "text" + */ + "type"?: string; + /** + * @default "" + */ + "value"?: string; + } + interface AdcSelect { + "onAdcChange"?: (event: AdcSelectCustomEvent) => void; + /** + * @default [] + */ + "options"?: SelectOption[]; + /** + * @default "Seleccione" + */ + "placeholder"?: string; + /** + * @default "" + */ + "value"?: string; + } + interface AdcShareButtons { + /** + * @default "" + */ + "description"?: string; + /** + * @default "" + */ + "title"?: string; + /** + * @default "" + */ + "url"?: string; + } + interface AdcSiteFooter { + /** + * @default "" + */ + "brandName"?: string; + /** + * @default "" + */ + "brandSlogan"?: string; + /** + * @default "" + */ + "creatorHref"?: string; + /** + * @default "" + */ + "creatorName"?: string; + /** + * @default false + */ + "lowerSign"?: boolean; + /** + * @default false + */ + "registered"?: boolean; + } + interface AdcSiteHeader { + /** + * @default "/" + */ + "homeHref"?: string; + /** + * @default "" + */ + "logoAlt"?: string; + /** + * @default "" + */ + "logoSrc"?: string; + } + interface AdcStarRating { + "average"?: number | null; + /** + * @default false + */ + "canRate"?: boolean; + "count"?: number | null; + "myRating"?: number | null; + "onAdcRate"?: (event: AdcStarRatingCustomEvent) => void; + /** + * @default false + */ + "pending"?: boolean; + } + interface AdcTableBlock { + "caption"?: string; + "columnAlign"?: Array<"left" | "center" | "right">; + /** + * @default [] + */ + "header"?: string[]; + /** + * @default false + */ + "rowHeaders"?: boolean; + /** + * @default [] + */ + "rows"?: string[][]; + } + interface AdcTestimonialCard { + /** + * @default "" + */ + "author"?: string; + /** + * @default true + */ + "staticRender"?: boolean; + } + interface AdcText { + /** + * @default true + */ + "contain"?: boolean; + /** + * @default true + */ + "staticRender"?: boolean; + } + /** + * Componente YouTube Facade para carga perezosa de videos + * Muestra una thumbnail clickeable en lugar de cargar el iframe inmediatamente + * Mejora performance al evitar cargar el player de YouTube hasta que sea necesario + */ + interface AdcYoutubeFacade { + /** + * Alto del contenedor (opcional, por defecto responsive 16:9) + */ + "height"?: string; + /** + * Título del video para accesibilidad + * @default "Video de YouTube" + */ + "title"?: string; + /** + * ID del video de YouTube (extraído de la URL) + */ + "videoId": string; + /** + * Ancho del contenedor (opcional, por defecto responsive) + */ + "width"?: string; + } + interface IntrinsicElements { + "adc-blocks-renderer": AdcBlocksRenderer; + "adc-button": AdcButton; + "adc-button-rounded": AdcButtonRounded; + "adc-callout": AdcCallout; + "adc-code-block": AdcCodeBlock; + "adc-content-card": AdcContentCard; + "adc-divider": AdcDivider; + "adc-dropdown-menu": AdcDropdownMenu; + "adc-feature-card": AdcFeatureCard; + "adc-icon-community": AdcIconCommunity; + "adc-icon-learning": AdcIconLearning; + "adc-icon-left-arrow": AdcIconLeftArrow; + "adc-icon-logout": AdcIconLogout; + "adc-icon-nitro": AdcIconNitro; + "adc-icon-opensource": AdcIconOpensource; + "adc-icon-pencil": AdcIconPencil; + "adc-icon-vip": AdcIconVip; + "adc-inline-tokens": AdcInlineTokens; + "adc-input": AdcInput; + "adc-list-block": AdcListBlock; + "adc-lp-badge": AdcLpBadge; + "adc-quote": AdcQuote; + "adc-search-input": AdcSearchInput; + "adc-select": AdcSelect; + "adc-share-buttons": AdcShareButtons; + "adc-site-footer": AdcSiteFooter; + "adc-site-header": AdcSiteHeader; + "adc-star-rating": AdcStarRating; + "adc-table-block": AdcTableBlock; + "adc-testimonial-card": AdcTestimonialCard; + "adc-text": AdcText; + "adc-youtube-facade": AdcYoutubeFacade; + } +} +export { LocalJSX as JSX }; +declare module "@stencil/core" { + export namespace JSX { + interface IntrinsicElements { + "adc-blocks-renderer": LocalJSX.AdcBlocksRenderer & JSXBase.HTMLAttributes; + "adc-button": LocalJSX.AdcButton & JSXBase.HTMLAttributes; + "adc-button-rounded": LocalJSX.AdcButtonRounded & JSXBase.HTMLAttributes; + "adc-callout": LocalJSX.AdcCallout & JSXBase.HTMLAttributes; + "adc-code-block": LocalJSX.AdcCodeBlock & JSXBase.HTMLAttributes; + "adc-content-card": LocalJSX.AdcContentCard & JSXBase.HTMLAttributes; + "adc-divider": LocalJSX.AdcDivider & JSXBase.HTMLAttributes; + "adc-dropdown-menu": LocalJSX.AdcDropdownMenu & JSXBase.HTMLAttributes; + "adc-feature-card": LocalJSX.AdcFeatureCard & JSXBase.HTMLAttributes; + "adc-icon-community": LocalJSX.AdcIconCommunity & JSXBase.HTMLAttributes; + "adc-icon-learning": LocalJSX.AdcIconLearning & JSXBase.HTMLAttributes; + "adc-icon-left-arrow": LocalJSX.AdcIconLeftArrow & JSXBase.HTMLAttributes; + "adc-icon-logout": LocalJSX.AdcIconLogout & JSXBase.HTMLAttributes; + "adc-icon-nitro": LocalJSX.AdcIconNitro & JSXBase.HTMLAttributes; + "adc-icon-opensource": LocalJSX.AdcIconOpensource & JSXBase.HTMLAttributes; + "adc-icon-pencil": LocalJSX.AdcIconPencil & JSXBase.HTMLAttributes; + "adc-icon-vip": LocalJSX.AdcIconVip & JSXBase.HTMLAttributes; + "adc-inline-tokens": LocalJSX.AdcInlineTokens & JSXBase.HTMLAttributes; + "adc-input": LocalJSX.AdcInput & JSXBase.HTMLAttributes; + "adc-list-block": LocalJSX.AdcListBlock & JSXBase.HTMLAttributes; + "adc-lp-badge": LocalJSX.AdcLpBadge & JSXBase.HTMLAttributes; + "adc-quote": LocalJSX.AdcQuote & JSXBase.HTMLAttributes; + "adc-search-input": LocalJSX.AdcSearchInput & JSXBase.HTMLAttributes; + "adc-select": LocalJSX.AdcSelect & JSXBase.HTMLAttributes; + "adc-share-buttons": LocalJSX.AdcShareButtons & JSXBase.HTMLAttributes; + "adc-site-footer": LocalJSX.AdcSiteFooter & JSXBase.HTMLAttributes; + "adc-site-header": LocalJSX.AdcSiteHeader & JSXBase.HTMLAttributes; + "adc-star-rating": LocalJSX.AdcStarRating & JSXBase.HTMLAttributes; + "adc-table-block": LocalJSX.AdcTableBlock & JSXBase.HTMLAttributes; + "adc-testimonial-card": LocalJSX.AdcTestimonialCard & JSXBase.HTMLAttributes; + "adc-text": LocalJSX.AdcText & JSXBase.HTMLAttributes; + /** + * Componente YouTube Facade para carga perezosa de videos + * Muestra una thumbnail clickeable en lugar de cargar el iframe inmediatamente + * Mejora performance al evitar cargar el player de YouTube hasta que sea necesario + */ + "adc-youtube-facade": LocalJSX.AdcYoutubeFacade & JSXBase.HTMLAttributes; + } + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-button-rounded/adc-button-rounded.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-button-rounded/adc-button-rounded.tsx new file mode 100644 index 00000000..5395cb45 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-button-rounded/adc-button-rounded.tsx @@ -0,0 +1,39 @@ +import { Component, Prop, h, Event, EventEmitter } from "@stencil/core"; + +@Component({ + tag: "adc-button-rounded", + shadow: false, +}) +export class AdcButtonRounded { + @Prop() type: "button" | "submit" | "reset" = "button"; + @Prop() href?: string; + @Prop() ariaLabel?: string; + + @Event() adcClick!: EventEmitter; + + private handleClick = (event: MouseEvent) => { + this.adcClick.emit(event); + }; + + private baseClass = + "rounded-full px-4 py-4 bg-primary text-tprimary shadow-cozy font-heading cursor-pointer hover:brightness-105 inline-block text-center font-semibold min-h-[44px] min-w-[44px] touch-manipulation flex items-center justify-center"; + + render() { + const TagName = this.href ? "a" : "button"; + const attrs = this.href + ? { + href: this.href, + target: "_blank", + rel: "noopener noreferrer", + } + : { + type: this.type, + }; + + return ( + + + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-button/adc-button.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-button/adc-button.tsx new file mode 100644 index 00000000..893aa67f --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-button/adc-button.tsx @@ -0,0 +1,43 @@ +import { Component, Prop, h, Event, EventEmitter } from "@stencil/core"; + +@Component({ + tag: "adc-button", + shadow: false, +}) +export class AdcButton { + @Prop() type: "button" | "submit" | "reset" = "button"; + @Prop() href?: string; + @Prop() ariaLabel?: string; + + @Event() adcClick!: EventEmitter; + + private handleClick = (event: MouseEvent) => { + this.adcClick.emit(event); + }; + + private baseClass = + "rounded-3xl px-8 py-4 bg-primary text-tprimary shadow-cozy font-heading cursor-pointer hover:brightness-105 inline-block text-center font-semibold min-h-[44px] min-w-[44px] touch-manipulation"; + + render() { + if (this.href) { + return ( + + + + ); + } + + return ( + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-callout/adc-callout.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-callout/adc-callout.tsx new file mode 100644 index 00000000..1ee6f213 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-callout/adc-callout.tsx @@ -0,0 +1,49 @@ +import { Component, Prop, h } from "@stencil/core"; + +@Component({ + tag: "adc-callout", + shadow: false, +}) +export class AdcCallout { + @Prop() tone: "info" | "warning" | "success" | "error" = "info"; + @Prop() role: "note" | "status" | "alert" = "note"; + + private getToneClass(): string { + switch (this.tone) { + case "warning": + return "bg-yellow-100/30 border-yellow-600 text-yellow-900"; + case "success": + return "bg-green-100/30 border-green-600 text-green-900"; + case "error": + return "bg-red-100/30 border-red-600 text-red-900"; + default: + return "bg-cyan-100/30 border-blue-600 text-blue-900"; + } + } + + render() { + const classes = `rounded-xxl border p-3 mb-2 ml-8 xl:max-w-[80vw] ${this.getToneClass()}`; + + if (this.role === "note") { + return ( + + ); + } + + if (this.role === "status") { + return ( + + + + ); + } + + return ( + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-code-block/adc-code-block.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-code-block/adc-code-block.tsx new file mode 100644 index 00000000..a0afaf56 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-code-block/adc-code-block.tsx @@ -0,0 +1,32 @@ +import { Component, Prop, h } from "@stencil/core"; + +@Component({ + tag: "adc-code-block", + shadow: false, +}) +export class AdcCodeBlock { + @Prop() language?: string; + @Prop() content: string = ""; + @Prop() ariaLabel?: string; + + private escapeHtml(text: string): string { + return text.replace(/&/g, "&").replace(//g, ">"); + } + + render() { + const escapedContent = this.escapeHtml(this.content); + const codeClass = this.language + ? `block p-3 whitespace-pre break-normal language-${this.language} lang-${this.language}` + : "block p-3 whitespace-pre break-normal"; + + return ( +
+ {this.language &&
[{this.language}]
} + +
+ ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-divider/adc-divider.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-divider/adc-divider.tsx new file mode 100644 index 00000000..44fc7d4b --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-divider/adc-divider.tsx @@ -0,0 +1,11 @@ +import { Component, h } from "@stencil/core"; + +@Component({ + tag: "adc-divider", + shadow: false, +}) +export class AdcDivider { + render() { + return
; + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-inline-tokens/adc-inline-tokens.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-inline-tokens/adc-inline-tokens.tsx new file mode 100644 index 00000000..dacefed1 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-inline-tokens/adc-inline-tokens.tsx @@ -0,0 +1,40 @@ +import { Component, Prop, h } from "@stencil/core"; + +export interface InlineToken { + type: "text" | "bold" | "italic" | "strike" | "code"; + content: string; +} + +@Component({ + tag: "adc-inline-tokens", + shadow: false, +}) +export class AdcInlineTokens { + @Prop() tokens: InlineToken[] = []; + @Prop() fallback: string = ""; + + render() { + if (!this.tokens || this.tokens.length === 0) { + return {this.fallback}; + } + + return ( + + {this.tokens.map((token, idx) => { + switch (token.type) { + case "bold": + return {token.content}; + case "italic": + return {token.content}; + case "strike": + return {token.content}; + case "code": + return {token.content}; + default: + return {token.content}; + } + })} + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-input/adc-input.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-input/adc-input.tsx new file mode 100644 index 00000000..f3cb6854 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-input/adc-input.tsx @@ -0,0 +1,38 @@ +import { Component, Prop, h, Event, EventEmitter } from "@stencil/core"; + +@Component({ + tag: "adc-input", + shadow: false, +}) +export class AdcInput { + @Prop() value: string = ""; + @Prop() placeholder?: string = ""; + @Prop() inputId?: string = ""; + @Prop() name?: string = ""; + @Prop() type?: string = "text"; + @Prop() autocomplete?: string = "off"; + @Prop() ariaLabel?: string = ""; + + @Event() adcInput!: EventEmitter; + + private handleInput = (event: Event) => { + const target = event.target as HTMLInputElement; + this.adcInput.emit(target.value); + }; + + render() { + return ( + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-list-block/adc-list-block.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-list-block/adc-list-block.tsx new file mode 100644 index 00000000..008108f4 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-list-block/adc-list-block.tsx @@ -0,0 +1,35 @@ +import { Component, Prop, h } from "@stencil/core"; + +@Component({ + tag: "adc-list-block", + shadow: false, +}) +export class AdcListBlock { + @Prop() ordered: boolean = false; + @Prop() items: string[] = []; + @Prop() start?: number; + @Prop() ariaLabel?: string; + + render() { + const listClass = "pl-5 my-2 list-outside mb-2 ml-16"; + const listItems = this.items.map((item, index) => ( +
  • + +
  • + )); + + if (this.ordered) { + return ( +
      + {listItems} +
    + ); + } + + return ( +
      + {listItems} +
    + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-lp-badge/adc-lp-badge.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-lp-badge/adc-lp-badge.tsx new file mode 100644 index 00000000..f065ef2e --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-lp-badge/adc-lp-badge.tsx @@ -0,0 +1,54 @@ +import { Component, Prop, h, Event, EventEmitter } from "@stencil/core"; + +@Component({ + tag: "adc-lp-badge", + shadow: false, +}) +export class AdcLpBadge { + @Prop() title: string = ""; + @Prop() color: string = ""; + @Prop() slug?: string; + @Prop() as?: "button" | "span"; + + @Event() adcClick!: EventEmitter; + + private handleClick = () => { + this.adcClick.emit(); + }; + + private getColorClasses(): string { + const colorMap = new Map([ + ["red", "bg-red-100 text-red-700 border-red-600"], + ["orange", "bg-orange-100 text-orange-700 border-orange-600"], + ["yellow", "bg-yellow-100 text-yellow-700 border-yellow-600"], + ["green", "bg-green-100 text-green-700 border-green-600"], + ["teal", "bg-teal-100 text-teal-700 border-teal-600"], + ["blue", "bg-blue-100 text-blue-700 border-blue-600"], + ["purple", "bg-purple-100 text-purple-700 border-purple-600"], + ["pink", "bg-pink-100 text-pink-700 border-pink-600"], + ]); + return colorMap.get(this.color) ?? ""; + } + + render() { + const baseClass = `px-2 py-1 min-w-fit rounded-full border text-sm no-underline inline-block flex items-center ${this.getColorClasses()}`; + + if (this.slug) { + return ( + + {this.title} + + ); + } + + if (this.as === "button") { + return ( + + ); + } + + return {this.title}; + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-quote/adc-quote.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-quote/adc-quote.tsx new file mode 100644 index 00000000..2e168f26 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-quote/adc-quote.tsx @@ -0,0 +1,20 @@ +import { Component, Prop, Element, h } from "@stencil/core"; + +@Component({ + tag: "adc-quote", + shadow: false, +}) +export class AdcQuote { + @Element() el!: HTMLElement; + @Prop() staticRender: boolean = true; + + render() { + // Captura las clases del host y aplícalas al blockquote interno + const hostClass = this.el.className; + return ( +
    + +
    + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-search-input/adc-search-input.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-search-input/adc-search-input.tsx new file mode 100644 index 00000000..dea9156a --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-search-input/adc-search-input.tsx @@ -0,0 +1,85 @@ +import { Component, Prop, h, Event, EventEmitter, State } from "@stencil/core"; + +@Component({ + tag: "adc-search-input", + shadow: false, +}) +export class AdcSearchInput { + @Prop() value: string = ""; + @Prop() placeholder: string = ""; + @Prop() inputId?: string = ""; + @Prop() name?: string = ""; + @Prop() type?: string = "text"; + @Prop() autocomplete?: string = "off"; + @Prop() ariaLabel?: string = ""; + @Prop() debounce: number = 300; + + @State() innerValue: string = ""; + + private debounceTimer?: ReturnType; + + @Event() adcInput!: EventEmitter; + + componentWillLoad() { + this.innerValue = this.value; + } + + private handleInput = (event: Event) => { + const target = event.target as HTMLInputElement; + this.innerValue = target.value; + + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + } + + if (this.debounce > 0) { + this.debounceTimer = setTimeout(() => { + this.adcInput.emit(this.innerValue); + }, this.debounce); + } else { + this.adcInput.emit(this.innerValue); + } + }; + + disconnectedCallback() { + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + } + } + + render() { + const labelClass = "relative flex items-center w-full bg-white rounded-xxl border border-surface"; + const iconClass = "absolute left-[0.9rem] inline-flex items-center justify-center text-black/45 pointer-events-none"; + const inputClass = + "flex-1 bg-transparent border-none outline-none py-[0.6rem] pr-[0.8rem] pl-[2.5rem] rounded-xxl font-text text-[0.9rem] text-text"; + + return ( + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-select/adc-select.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-select/adc-select.tsx new file mode 100644 index 00000000..561b01db --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-select/adc-select.tsx @@ -0,0 +1,78 @@ +import { Component, Prop, h, Event, EventEmitter, State } from "@stencil/core"; + +export interface SelectOption { + label: string; + value: string; +} + +@Component({ + tag: "adc-select", + shadow: false, +}) +export class AdcSelect { + @Prop() value: string = ""; + @Prop() options: SelectOption[] = []; + @Prop() placeholder: string = "Seleccione"; + + @State() isOpen: boolean = false; + + @Event() adcChange!: EventEmitter; + + private handleToggle = () => { + this.isOpen = !this.isOpen; + }; + + private handleSelect = (option: SelectOption) => { + this.adcChange.emit(option.value); + this.isOpen = false; + }; + + private handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + this.isOpen = false; + } + }; + + private getSelectedLabel(): string { + const selected = this.options.find((opt) => opt.value === this.value); + return selected ? selected.label : this.placeholder; + } + + render() { + return ( +
    + + {this.isOpen && ( +
    + {this.options.map((option) => ( + + ))} +
    + )} +
    + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-star-rating/adc-star-rating.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-star-rating/adc-star-rating.tsx new file mode 100644 index 00000000..8f9eac3a --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-star-rating/adc-star-rating.tsx @@ -0,0 +1,61 @@ +import { Component, Prop, h, Event, EventEmitter, State } from "@stencil/core"; + +@Component({ + tag: "adc-star-rating", + shadow: false, +}) +export class AdcStarRating { + @Prop() average?: number | null; + @Prop() count?: number | null; + @Prop() myRating?: number | null; + @Prop() canRate: boolean = false; + @Prop() pending: boolean = false; + + @State() mounted: boolean = false; + + @Event() adcRate!: EventEmitter; + + componentDidLoad() { + this.mounted = true; + } + + private handleClick = (rating: number) => { + if (!this.canRate || this.pending || !this.mounted) return; + this.adcRate.emit(rating); + }; + + private isDisabled(): boolean { + return !this.canRate || this.pending || !this.mounted; + } + + private getDisplayRating(): number { + return this.myRating ?? this.average ?? 0; + } + + render() { + const disabled = this.isDisabled(); + const displayRating = this.getDisplayRating(); + + return ( +
    + {[1, 2, 3, 4, 5].map((i) => ( + + ))} + + {typeof this.average === "number" ? this.average.toFixed(1) : "0.0"} ({this.count ?? 0}) + +
    + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-table-block/adc-table-block.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-table-block/adc-table-block.tsx new file mode 100644 index 00000000..f6432442 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-table-block/adc-table-block.tsx @@ -0,0 +1,58 @@ +import { Component, Prop, h } from "@stencil/core"; + +@Component({ + tag: "adc-table-block", + shadow: false, +}) +export class AdcTableBlock { + @Prop() header: string[] = []; + @Prop() rows: string[][] = []; + @Prop() columnAlign?: Array<"left" | "center" | "right">; + @Prop() caption?: string; + @Prop() rowHeaders: boolean = false; + + private getAlignClass(align?: "left" | "center" | "right"): string { + if (align === "center") return "text-center"; + if (align === "right") return "text-right"; + return "text-left"; + } + + render() { + return ( +
    + + {this.caption && } + + + {this.header.map((h, i) => ( + + ))} + + + + {this.rows.map((row, ri) => ( + + {row.map((cell, ci) => { + if (this.rowHeaders && ci === 0) { + return ( + + ); + } + return ( + + ); + })} + + ))} + +
    {this.caption}
    + +
    + + + +
    +
    + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/atoms/adc-text/adc-text.tsx b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-text/adc-text.tsx new file mode 100644 index 00000000..210117bb --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/atoms/adc-text/adc-text.tsx @@ -0,0 +1,18 @@ +import { Component, Prop, h } from "@stencil/core"; + +@Component({ + tag: "adc-text", + shadow: false, +}) +export class AdcText { + @Prop() staticRender: boolean = true; + @Prop() contain: boolean = true; + + render() { + return ( +

    + +

    + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-community/adc-icon-community.css b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-community/adc-icon-community.css new file mode 100644 index 00000000..24da728d --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-community/adc-icon-community.css @@ -0,0 +1,10 @@ +:host { + display: inline-flex; + align-items: center; + justify-content: center; + color: rgb(93, 58, 47); +} + +.adc-icon { + display: block; +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-community/adc-icon-community.tsx b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-community/adc-icon-community.tsx new file mode 100644 index 00000000..3a58a29e --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-community/adc-icon-community.tsx @@ -0,0 +1,26 @@ +import { Component, Prop, h, Host } from "@stencil/core"; + +@Component({ + tag: "adc-icon-community", + styleUrl: "adc-icon-community.css", + shadow: true, +}) +export class AdcIconCommunity { + @Prop() size: string = "2rem"; + + render() { + return ( + + + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-learning/adc-icon-learning.css b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-learning/adc-icon-learning.css new file mode 100644 index 00000000..24da728d --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-learning/adc-icon-learning.css @@ -0,0 +1,10 @@ +:host { + display: inline-flex; + align-items: center; + justify-content: center; + color: rgb(93, 58, 47); +} + +.adc-icon { + display: block; +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-learning/adc-icon-learning.tsx b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-learning/adc-icon-learning.tsx new file mode 100644 index 00000000..90208ffb --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-learning/adc-icon-learning.tsx @@ -0,0 +1,28 @@ +import { Component, Prop, h, Host } from "@stencil/core"; + +@Component({ + tag: "adc-icon-learning", + styleUrl: "adc-icon-learning.css", + shadow: true, +}) +export class AdcIconLearning { + @Prop() size: string = "2rem"; + + render() { + return ( + + + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-left-arrow/adc-icon-left-arrow.css b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-left-arrow/adc-icon-left-arrow.css new file mode 100644 index 00000000..febfe7dc --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-left-arrow/adc-icon-left-arrow.css @@ -0,0 +1,10 @@ +:host { + display: inline-flex; + align-items: center; + justify-content: center; + color: currentColor; +} + +.adc-icon { + display: block; +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-left-arrow/adc-icon-left-arrow.tsx b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-left-arrow/adc-icon-left-arrow.tsx new file mode 100644 index 00000000..36852597 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-left-arrow/adc-icon-left-arrow.tsx @@ -0,0 +1,29 @@ +import { Component, Prop, h, Host } from "@stencil/core"; + +@Component({ + tag: "adc-icon-left-arrow", + styleUrl: "adc-icon-left-arrow.css", + shadow: true, +}) +export class AdcIconLeftArrow { + @Prop() size: string = "1rem"; + + render() { + return ( + + + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-logout/adc-icon-logout.css b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-logout/adc-icon-logout.css new file mode 100644 index 00000000..febfe7dc --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-logout/adc-icon-logout.css @@ -0,0 +1,10 @@ +:host { + display: inline-flex; + align-items: center; + justify-content: center; + color: currentColor; +} + +.adc-icon { + display: block; +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-logout/adc-icon-logout.tsx b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-logout/adc-icon-logout.tsx new file mode 100644 index 00000000..7672604d --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-logout/adc-icon-logout.tsx @@ -0,0 +1,30 @@ +import { Component, Prop, h, Host } from "@stencil/core"; + +@Component({ + tag: "adc-icon-logout", + styleUrl: "adc-icon-logout.css", + shadow: true, +}) +export class AdcIconLogout { + @Prop() size: string = "1rem"; + + render() { + return ( + + + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-nitro/adc-icon-nitro.css b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-nitro/adc-icon-nitro.css new file mode 100644 index 00000000..febfe7dc --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-nitro/adc-icon-nitro.css @@ -0,0 +1,10 @@ +:host { + display: inline-flex; + align-items: center; + justify-content: center; + color: currentColor; +} + +.adc-icon { + display: block; +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-nitro/adc-icon-nitro.tsx b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-nitro/adc-icon-nitro.tsx new file mode 100644 index 00000000..da8eab0c --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-nitro/adc-icon-nitro.tsx @@ -0,0 +1,30 @@ +import { Component, Prop, h, Host } from "@stencil/core"; + +@Component({ + tag: "adc-icon-nitro", + styleUrl: "adc-icon-nitro.css", + shadow: true, +}) +export class AdcIconNitro { + @Prop() size: string = "1rem"; + + render() { + return ( + + + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-opensource/adc-icon-opensource.css b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-opensource/adc-icon-opensource.css new file mode 100644 index 00000000..24da728d --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-opensource/adc-icon-opensource.css @@ -0,0 +1,10 @@ +:host { + display: inline-flex; + align-items: center; + justify-content: center; + color: rgb(93, 58, 47); +} + +.adc-icon { + display: block; +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-opensource/adc-icon-opensource.tsx b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-opensource/adc-icon-opensource.tsx new file mode 100644 index 00000000..893b59b9 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-opensource/adc-icon-opensource.tsx @@ -0,0 +1,26 @@ +import { Component, Prop, h, Host } from "@stencil/core"; + +@Component({ + tag: "adc-icon-opensource", + styleUrl: "adc-icon-opensource.css", + shadow: true, +}) +export class AdcIconOpensource { + @Prop() size: string = "2rem"; + + render() { + return ( + + + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-pencil/adc-icon-pencil.css b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-pencil/adc-icon-pencil.css new file mode 100644 index 00000000..febfe7dc --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-pencil/adc-icon-pencil.css @@ -0,0 +1,10 @@ +:host { + display: inline-flex; + align-items: center; + justify-content: center; + color: currentColor; +} + +.adc-icon { + display: block; +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-pencil/adc-icon-pencil.tsx b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-pencil/adc-icon-pencil.tsx new file mode 100644 index 00000000..35974ec9 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-pencil/adc-icon-pencil.tsx @@ -0,0 +1,34 @@ +import { Component, Prop, h, Host } from "@stencil/core"; + +@Component({ + tag: "adc-icon-pencil", + styleUrl: "adc-icon-pencil.css", + shadow: true, +}) +export class AdcIconPencil { + @Prop() size: string = "1rem"; + + render() { + return ( + + + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-vip/adc-icon-vip.css b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-vip/adc-icon-vip.css new file mode 100644 index 00000000..febfe7dc --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-vip/adc-icon-vip.css @@ -0,0 +1,10 @@ +:host { + display: inline-flex; + align-items: center; + justify-content: center; + color: currentColor; +} + +.adc-icon { + display: block; +} diff --git a/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-vip/adc-icon-vip.tsx b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-vip/adc-icon-vip.tsx new file mode 100644 index 00000000..c1dab609 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/icons/adc-icon-vip/adc-icon-vip.tsx @@ -0,0 +1,29 @@ +import { Component, Prop, h, Host } from "@stencil/core"; + +@Component({ + tag: "adc-icon-vip", + styleUrl: "adc-icon-vip.css", + shadow: true, +}) +export class AdcIconVip { + @Prop() size: string = "1rem"; + + render() { + return ( + + + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/molecules/adc-content-card/adc-content-card.tsx b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-content-card/adc-content-card.tsx new file mode 100644 index 00000000..83628df8 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-content-card/adc-content-card.tsx @@ -0,0 +1,58 @@ +import { Component, Prop, h, Event, EventEmitter } from "@stencil/core"; + +@Component({ + tag: "adc-content-card", + shadow: false, +}) +export class AdcContentCard { + @Prop() title: string = ""; + @Prop() description?: string; + @Prop() bannerUrl?: string; + @Prop() bannerAlt?: string; + @Prop() href?: string; + @Prop() compact: boolean = false; + + @Event() cardClick!: EventEmitter; + + private handleClick = (event: MouseEvent) => { + this.cardClick.emit(event); + }; + + render() { + const rootClass = "relative block h-full rounded-xxl cursor-pointer no-underline group max-w-lg"; + const surfaceClass = + "absolute inset-0 min-h-full bg-surface rounded-xxl shadow-cozy " + + "motion-safe:transition-transform motion-safe:duration-200 motion-safe:ease-out " + + "group-hover:scale-y-[1.05]"; + + const innerClass = "relative p-4 pb-5 text-text space-y-2 min-h-full flex flex-col justify-start"; + + const imageClass = `w-full object-cover rounded-xxl ${this.compact ? "h-24" : "h-40"}`; + + const content = ( +
    + {this.bannerUrl && {this.bannerAlt} + {this.description &&

    {this.title}

    } + {!this.description &&

    {this.title}

    } + {this.description &&

    {this.description}

    } + +
    + ); + + if (this.href) { + return ( + +
    + {content} +
    + ); + } + + return ( +
    +
    + {content} +
    + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/molecules/adc-dropdown-menu/adc-dropdown-menu.tsx b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-dropdown-menu/adc-dropdown-menu.tsx new file mode 100644 index 00000000..4596ffd6 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-dropdown-menu/adc-dropdown-menu.tsx @@ -0,0 +1,112 @@ +import { Component, Prop, h, Event, EventEmitter, State } from "@stencil/core"; + +export interface DropdownMenuItem { + label: string; + to?: string; + action?: string; + icon?: any; +} + +@Component({ + tag: "adc-dropdown-menu", + shadow: false, +}) +export class AdcDropdownMenu { + @Prop() items: DropdownMenuItem[] = []; + @Prop() alignState: "left" | "right" = "left"; + @Prop() openOnHover: boolean = true; + + @State() isOpen: boolean = false; + + @Event() adcItemClick!: EventEmitter; + + private hoverTimeout?: ReturnType; + + private handleToggle = () => { + this.isOpen = !this.isOpen; + }; + + private handleItemClick = (item: DropdownMenuItem) => { + this.adcItemClick.emit(item); + this.isOpen = false; + }; + + private handleMouseEnter = () => { + if (!this.openOnHover) return; + if (this.hoverTimeout) clearTimeout(this.hoverTimeout); + this.isOpen = true; + }; + + private handleMouseLeave = () => { + if (!this.openOnHover) return; + this.hoverTimeout = setTimeout(() => { + this.isOpen = false; + }, 150); + }; + + private handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + this.isOpen = false; + } + }; + + disconnectedCallback() { + if (this.hoverTimeout) clearTimeout(this.hoverTimeout); + } + + render() { + const alignClass = this.alignState === "right" ? "right-0" : "left-0"; + + return ( +
    + + + {this.isOpen && ( + + )} +
    + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/molecules/adc-feature-card/adc-feature-card.tsx b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-feature-card/adc-feature-card.tsx new file mode 100644 index 00000000..961e788d --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-feature-card/adc-feature-card.tsx @@ -0,0 +1,24 @@ +import { Component, Prop, h } from "@stencil/core"; + +@Component({ + tag: "adc-feature-card", + shadow: false, +}) +export class AdcFeatureCard { + @Prop() title: string = ""; + @Prop() staticRender: boolean = true; + + render() { + return ( +
    + +

    {this.title}

    + + + +
    + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/molecules/adc-share-buttons/adc-share-buttons.tsx b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-share-buttons/adc-share-buttons.tsx new file mode 100644 index 00000000..e224bdce --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-share-buttons/adc-share-buttons.tsx @@ -0,0 +1,71 @@ +import { Component, Prop, h } from "@stencil/core"; + +@Component({ + tag: "adc-share-buttons", + shadow: false, +}) +export class AdcShareButtons { + @Prop() title: string = ""; + @Prop() description: string = ""; + @Prop() url: string = ""; + + private getEncodedText(): string { + return encodeURIComponent(`${this.title}\n\n${this.description}\n\n${this.url}`); + } + + private getLinkedInUrl(): string { + return `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(this.url)}`; + } + + private getTwitterUrl(): string { + return `https://twitter.com/intent/tweet?text=${this.getEncodedText()}`; + } + + private getThreadsUrl(): string { + return `https://www.threads.net/intent/post?text=${this.getEncodedText()}`; + } + + render() { + return ( + + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/molecules/adc-testimonial-card/adc-testimonial-card.tsx b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-testimonial-card/adc-testimonial-card.tsx new file mode 100644 index 00000000..bcc2e456 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-testimonial-card/adc-testimonial-card.tsx @@ -0,0 +1,21 @@ +import { Component, Prop, h } from "@stencil/core"; + +@Component({ + tag: "adc-testimonial-card", + shadow: false, +}) +export class AdcTestimonialCard { + @Prop() author: string = ""; + @Prop() staticRender: boolean = true; + + render() { + return ( +
    + + + +
    — {this.author}
    +
    + ); + } +} diff --git a/src/apps/public/00-adc-ui-library/src/components/molecules/adc-youtube-facade/adc-youtube-facade.tsx b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-youtube-facade/adc-youtube-facade.tsx new file mode 100644 index 00000000..57503882 --- /dev/null +++ b/src/apps/public/00-adc-ui-library/src/components/molecules/adc-youtube-facade/adc-youtube-facade.tsx @@ -0,0 +1,94 @@ +import { Component, Prop, State, h } from "@stencil/core"; + +/** + * Componente YouTube Facade para carga perezosa de videos + * Muestra una thumbnail clickeable en lugar de cargar el iframe inmediatamente + * Mejora performance al evitar cargar el player de YouTube hasta que sea necesario + */ +@Component({ + tag: "adc-youtube-facade", + shadow: false, +}) +export class AdcYoutubeFacade { + /** ID del video de YouTube (extraído de la URL) */ + @Prop() videoId!: string; + + /** Título del video para accesibilidad */ + @Prop() title: string = "Video de YouTube"; + + /** Ancho del contenedor (opcional, por defecto responsive) */ + @Prop() width?: string; + + /** Alto del contenedor (opcional, por defecto responsive 16:9) */ + @Prop() height?: string; + + /** Estado que controla si el iframe está activo */ + @State() activated: boolean = false; + + /** + * Activa el iframe cuando el usuario hace click + */ + private activate = () => { + this.activated = true; + }; + + /** + * Maneja el evento de teclado para accesibilidad + */ + private handleKeyPress = (event: KeyboardEvent) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + this.activate(); + } + }; + + render() { + if (this.activated) { + // Renderizar iframe cuando está activado + return ( +