@reflow-js/templates 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/mfe/application.json +17 -0
- package/mfe/jest.config.js +7 -0
- package/mfe/package.json +18 -0
- package/mfe/public/index.html +21 -0
- package/mfe/reflow.config.json +35 -0
- package/mfe/src/index.ts +18 -0
- package/mfe/src/style.css +13 -0
- package/mfe/src/welcome-component.test.ts +38 -0
- package/mfe/src/welcome-component.ts +52 -0
- package/mfe/tsconfig.json +16 -0
- package/mfe-angular/README.md +14 -0
- package/mfe-angular/public/index.html +21 -0
- package/mfe-angular/reflow.config.json +23 -0
- package/mfe-angular/src/app.component.html +3 -0
- package/mfe-angular/src/components/welcome.component.ts +283 -0
- package/mfe-angular/src/index.ts +44 -0
- package/mfe-angular/src/styles.css +25 -0
- package/mfe-angular/tsconfig.json +16 -0
- package/mfe-react/README.md +0 -0
- package/mfe-react/application.json +5 -0
- package/mfe-react/public/index.html +22 -0
- package/mfe-react/reflow.config.json +34 -0
- package/mfe-react/src/App.tsx +21 -0
- package/mfe-react/src/components/welcome/welcome.component.tsx +74 -0
- package/mfe-react/src/components/welcome/welcome.style.css +224 -0
- package/mfe-react/src/index.css +25 -0
- package/mfe-react/src/index.tsx +39 -0
- package/mfe-react/tsconfig.json +19 -0
- package/mfe-react-tailwind/README.md +0 -0
- package/mfe-react-tailwind/application.json +5 -0
- package/mfe-react-tailwind/postcss.config.js +6 -0
- package/mfe-react-tailwind/public/index.html +22 -0
- package/mfe-react-tailwind/reflow.config.json +34 -0
- package/mfe-react-tailwind/src/App.tsx +21 -0
- package/mfe-react-tailwind/src/components/welcome/welcome.component.tsx +74 -0
- package/mfe-react-tailwind/src/components/welcome/welcome.style.css +224 -0
- package/mfe-react-tailwind/src/index.css +29 -0
- package/mfe-react-tailwind/src/index.tsx +39 -0
- package/mfe-react-tailwind/tailwind.config.js +11 -0
- package/mfe-react-tailwind/tsconfig.json +19 -0
- package/mfe-vue/README.md +14 -0
- package/mfe-vue/public/application.json +7 -0
- package/mfe-vue/public/index.html +21 -0
- package/mfe-vue/reflow.config.json +23 -0
- package/mfe-vue/src/App.vue +16 -0
- package/mfe-vue/src/components/Welcome.vue +307 -0
- package/mfe-vue/src/index.css +5 -0
- package/mfe-vue/src/index.ts +16 -0
- package/mfe-vue/tsconfig.json +14 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @reflow-js/templates
|
|
2
|
+
|
|
3
|
+
Project scaffolding templates consumed by [`@reflow-js/cli`](../cli).
|
|
4
|
+
|
|
5
|
+
Each folder is a self-contained scaffold copied verbatim (with `{{name}}`
|
|
6
|
+
substitution) when a user runs `reflow generate mfe`:
|
|
7
|
+
|
|
8
|
+
| Folder | Template (`--template` / `--css`) | Ships tests |
|
|
9
|
+
|--------|-----------------------------------|:-----------:|
|
|
10
|
+
| `mfe` | `core` (native) | ✅ jest smoke test |
|
|
11
|
+
| `mfe-react` | `react` | ⬜ planned |
|
|
12
|
+
| `mfe-react-tailwind` | `react` + `--css tailwind` | ⬜ planned |
|
|
13
|
+
| `mfe-angular` | `angular` | ⬜ planned |
|
|
14
|
+
| `mfe-vue` | `vue` | ⬜ planned |
|
|
15
|
+
|
|
16
|
+
The CLI wires the jest toolchain (`test` script + devDependencies) into a generated
|
|
17
|
+
project only when its scaffold ships at least one `*.test.*` file — so a template without
|
|
18
|
+
tests never produces a failing `npm test`. Adding a test file to any folder above
|
|
19
|
+
auto-enables jest for that template; no CLI change needed.
|
|
20
|
+
|
|
21
|
+
This package is data-only: it ships no runtime code. The CLI resolves it via
|
|
22
|
+
`require.resolve('@reflow-js/templates/package.json')` and falls back to the sibling
|
|
23
|
+
package during monorepo development.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app": {
|
|
3
|
+
"name": "{{name}}",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"environment": "development"
|
|
6
|
+
},
|
|
7
|
+
"api": {
|
|
8
|
+
"base": {
|
|
9
|
+
"url": "http://localhost:8080/api"
|
|
10
|
+
},
|
|
11
|
+
"timeout": 5000
|
|
12
|
+
},
|
|
13
|
+
"feature": {
|
|
14
|
+
"new_ui": true,
|
|
15
|
+
"beta_features": false
|
|
16
|
+
}
|
|
17
|
+
}
|
package/mfe/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"reflow": "reflow",
|
|
7
|
+
"dev": "reflow dev",
|
|
8
|
+
"build": "reflow build",
|
|
9
|
+
"preview": "reflow preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@reflow-js/core": "{{version}}",
|
|
13
|
+
"@reflow-js/cli": "{{version}}"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.0.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{name}} - Reflow MFE</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app">
|
|
10
|
+
<h1>Reflow MFE: {{name}}</h1>
|
|
11
|
+
<div class="mfe-container">
|
|
12
|
+
<!-- Render our welcome component -->
|
|
13
|
+
<mfe-welcome name="Developer"></mfe-welcome>
|
|
14
|
+
|
|
15
|
+
<p>Welcome to your new Microfrontend.</p>
|
|
16
|
+
<slot-container></slot-container>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
21
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Microfrontend generated by Reflow CLI",
|
|
5
|
+
"buildEngine": "webpack",
|
|
6
|
+
"bundler": {
|
|
7
|
+
"input": "src/index.ts",
|
|
8
|
+
"output": "dist",
|
|
9
|
+
"format": "umd",
|
|
10
|
+
"minify": true,
|
|
11
|
+
"sourcemap": true
|
|
12
|
+
},
|
|
13
|
+
"components": {
|
|
14
|
+
"prefix": "rf",
|
|
15
|
+
"srcDir": "src/components",
|
|
16
|
+
"outDir": "dist/components",
|
|
17
|
+
"registry": "dist/registry.js"
|
|
18
|
+
},
|
|
19
|
+
"devServer": {
|
|
20
|
+
"port": 3000,
|
|
21
|
+
"host": "localhost",
|
|
22
|
+
"open": true,
|
|
23
|
+
"hot": true,
|
|
24
|
+
"historyApiFallback": true
|
|
25
|
+
},
|
|
26
|
+
"styling": {
|
|
27
|
+
"framework": "native",
|
|
28
|
+
"shadowDom": true,
|
|
29
|
+
"injectMode": "constructable"
|
|
30
|
+
},
|
|
31
|
+
"security": {
|
|
32
|
+
"cors": false,
|
|
33
|
+
"allowedOrigins": []
|
|
34
|
+
}
|
|
35
|
+
}
|
package/mfe/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microfrontend Entry Point: {{name}}
|
|
3
|
+
*/
|
|
4
|
+
import { Reflow } from '@reflow-js/core';
|
|
5
|
+
import './style.css';
|
|
6
|
+
import { defineWelcomeComponent } from './welcome-component';
|
|
7
|
+
|
|
8
|
+
// Initialize Reflow system, then register this microfrontend's components.
|
|
9
|
+
Reflow.init({
|
|
10
|
+
propertiesFile: 'application.json'
|
|
11
|
+
}).then(() => {
|
|
12
|
+
console.log('Reflow initialized successfully');
|
|
13
|
+
defineWelcomeComponent();
|
|
14
|
+
}).catch(e => {
|
|
15
|
+
console.error('Reflow initialization failed', e);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
console.log('🚀 {{name}} Microfrontend Loaded');
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke tests for the {{name}} microfrontend's Welcome component.
|
|
3
|
+
*
|
|
4
|
+
* These run against jsdom with no network install of @reflow-js/core required — they
|
|
5
|
+
* exercise the component in isolation: custom-element registration and shadow-DOM render.
|
|
6
|
+
* Use them as the seed for your own render/behavior tests.
|
|
7
|
+
*/
|
|
8
|
+
import { WelcomeComponent, WELCOME_TAG, defineWelcomeComponent } from './welcome-component';
|
|
9
|
+
|
|
10
|
+
describe('WelcomeComponent', () => {
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
defineWelcomeComponent();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('registers the custom element under its tag', () => {
|
|
16
|
+
expect(customElements.get(WELCOME_TAG)).toBe(WelcomeComponent);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('renders a shadow root when connected', () => {
|
|
20
|
+
const el = document.createElement(WELCOME_TAG);
|
|
21
|
+
document.body.appendChild(el);
|
|
22
|
+
|
|
23
|
+
expect(el.shadowRoot).not.toBeNull();
|
|
24
|
+
expect(el.shadowRoot?.querySelector('.welcome-card')).not.toBeNull();
|
|
25
|
+
|
|
26
|
+
el.remove();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('reflects the name attribute into the rendered output', () => {
|
|
30
|
+
const el = document.createElement(WELCOME_TAG);
|
|
31
|
+
el.setAttribute('name', 'Ada');
|
|
32
|
+
document.body.appendChild(el);
|
|
33
|
+
|
|
34
|
+
expect(el.shadowRoot?.innerHTML).toContain('Ada');
|
|
35
|
+
|
|
36
|
+
el.remove();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Welcome Component for the {{name}} microfrontend.
|
|
3
|
+
*
|
|
4
|
+
* Kept in its own module (no framework side effects) so it can be unit-tested in
|
|
5
|
+
* isolation — see welcome-component.test.ts. The entry point (index.ts) is responsible
|
|
6
|
+
* for bootstrapping Reflow; this file only defines and registers the element.
|
|
7
|
+
*/
|
|
8
|
+
export const WELCOME_TAG = 'mfe-welcome';
|
|
9
|
+
|
|
10
|
+
export class WelcomeComponent extends HTMLElement {
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.attachShadow({ mode: 'open' });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
connectedCallback() {
|
|
17
|
+
const name = this.getAttribute('name') || 'User';
|
|
18
|
+
this.render(name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
render(name: string) {
|
|
22
|
+
if (!this.shadowRoot) return;
|
|
23
|
+
this.shadowRoot.innerHTML = `
|
|
24
|
+
<style>
|
|
25
|
+
.welcome-card {
|
|
26
|
+
padding: 2rem;
|
|
27
|
+
border-radius: 8px;
|
|
28
|
+
background: #f3f4f6;
|
|
29
|
+
border: 2px solid #3b82f6;
|
|
30
|
+
font-family: sans-serif;
|
|
31
|
+
text-align: center;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
34
|
+
<rf-container class="welcome-card">
|
|
35
|
+
<rf-text type="h2" label="Welcome to {{name}}!" style="color: #1e40af;"></rf-text>
|
|
36
|
+
<rf-text type="p" label="Hello, ${name}. This is your new Microfrontend component."></rf-text>
|
|
37
|
+
<rf-flex gap="10px" justify-content="center" style="margin-top: 1rem;">
|
|
38
|
+
<rf-button label="Get Started" variant="primary"></rf-button>
|
|
39
|
+
<rf-button label="Learn More" variant="outline"></rf-button>
|
|
40
|
+
</rf-flex>
|
|
41
|
+
<slot></slot>
|
|
42
|
+
</rf-container>
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Registers the component under WELCOME_TAG, guarding against duplicate definitions. */
|
|
48
|
+
export function defineWelcomeComponent() {
|
|
49
|
+
if (!customElements.get(WELCOME_TAG)) {
|
|
50
|
+
customElements.define(WELCOME_TAG, WelcomeComponent);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Plantilla Angular MFE + Reflow-js
|
|
2
|
+
|
|
3
|
+
Esta plantilla muestra cómo consumir un Web Component generado por Reflow-js en una aplicación Angular.
|
|
4
|
+
|
|
5
|
+
## Uso básico
|
|
6
|
+
|
|
7
|
+
1. Asegúrate de que el bundle de tu microfrontend (MFE) de Reflow-js esté disponible y cargado en tu `index.html` (por ejemplo, vía `<script src="/dist/mi-mfe/registry/registry.js"></script>`).
|
|
8
|
+
2. Usa el Web Component directamente en tus templates Angular:
|
|
9
|
+
|
|
10
|
+
```html
|
|
11
|
+
<welcome-card title="¡Hola desde Angular!" description="Este componente es un Web Component generado por Reflow-js."></welcome-card>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
No necesitas wrappers ni librerías adicionales. Los eventos y propiedades funcionan como atributos estándar de HTML.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Angular MFE + Reflow-js</title>
|
|
7
|
+
<!-- Ejemplo: cargar el script con src apuntando al registry.js generado -->
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<!--
|
|
11
|
+
Reflow registra 'welcome-card' en src/index.ts:
|
|
12
|
+
Reflow.define('welcome-card', WelcomeComponent, { shadow: true, framework: 'angular' })
|
|
13
|
+
Las props se pasan como atributos HTML.
|
|
14
|
+
-->
|
|
15
|
+
<welcome-card
|
|
16
|
+
title="{{name}} corriendo"
|
|
17
|
+
description="Componente Angular encapsulado con Shadow DOM por Reflow-js."
|
|
18
|
+
name="Developer">
|
|
19
|
+
</welcome-card>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"buildEngine": "webpack",
|
|
5
|
+
"bundler": {
|
|
6
|
+
"input": "src/index.ts",
|
|
7
|
+
"output": "dist",
|
|
8
|
+
"format": "umd",
|
|
9
|
+
"minify": false,
|
|
10
|
+
"sourcemap": true
|
|
11
|
+
},
|
|
12
|
+
"components": {
|
|
13
|
+
"srcDir": "src/components",
|
|
14
|
+
"outDir": "dist/components",
|
|
15
|
+
"prefix": "mfe",
|
|
16
|
+
"registry": "dist/registry/registry.js"
|
|
17
|
+
},
|
|
18
|
+
"dev": {
|
|
19
|
+
"port": 3002,
|
|
20
|
+
"hotReload": true,
|
|
21
|
+
"open": true
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import { Component, Input, ViewEncapsulation } from '@angular/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* WelcomeComponent — Angular
|
|
7
|
+
*
|
|
8
|
+
* Reflow registra este componente como Custom Element en src/index.ts:
|
|
9
|
+
* Reflow.define('welcome-card', WelcomeComponent, defaultProps, { shadow: true, framework: 'angular' })
|
|
10
|
+
*
|
|
11
|
+
* Las props (@Input) se pasan como atributos HTML:
|
|
12
|
+
* <welcome-card title="Hola" description="..." name="Dev"></welcome-card>
|
|
13
|
+
*/
|
|
14
|
+
@Component({
|
|
15
|
+
selector: 'app-welcome',
|
|
16
|
+
standalone: true,
|
|
17
|
+
encapsulation: ViewEncapsulation.ShadowDom,
|
|
18
|
+
template: `
|
|
19
|
+
<div class="rf-card">
|
|
20
|
+
<!-- Ambient glow -->
|
|
21
|
+
<div class="rf-glow"></div>
|
|
22
|
+
|
|
23
|
+
<!-- Top border accent -->
|
|
24
|
+
<div class="rf-card__top-bar"></div>
|
|
25
|
+
|
|
26
|
+
<div class="rf-card__inner">
|
|
27
|
+
<!-- Header row: icon + badge -->
|
|
28
|
+
<div class="rf-card__header">
|
|
29
|
+
<div class="rf-icon-wrap">
|
|
30
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 223 236" width="32" class="angular-logo"><g clip-path="url(#a)"><path fill="url(#b)" d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"></path><path fill="url(#c)" d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"></path></g><defs><linearGradient id="b" x1="49.009" x2="225.829" y1="213.75" y2="129.722" gradientUnits="userSpaceOnUse"><stop stop-color="#E40035"></stop><stop offset=".24" stop-color="#F60A48"></stop><stop offset=".352" stop-color="#F20755"></stop><stop offset=".494" stop-color="#DC087D"></stop><stop offset=".745" stop-color="#9717E7"></stop><stop offset="1" stop-color="#6C00F5"></stop></linearGradient><linearGradient id="c" x1="41.025" x2="156.741" y1="28.344" y2="160.344" gradientUnits="userSpaceOnUse"><stop stop-color="#FF31D9"></stop><stop offset="1" stop-color="#FF5BE1" stop-opacity="0"></stop></linearGradient><clipPath id="a"><path fill="#fff" d="M0 0h223v236H0z"></path></clipPath></defs></svg>
|
|
31
|
+
</div>
|
|
32
|
+
<span class="rf-badge">Angular MFE</span>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<!-- Title & description -->
|
|
36
|
+
<h1 class="rf-card__title">{{ title }}</h1>
|
|
37
|
+
<p class="rf-card__desc">{{ description }}</p>
|
|
38
|
+
|
|
39
|
+
<!-- Divider -->
|
|
40
|
+
<div class="rf-divider"></div>
|
|
41
|
+
|
|
42
|
+
<!-- Greeting -->
|
|
43
|
+
<div class="rf-greeting">
|
|
44
|
+
<div class="rf-avatar">{{ name.charAt(0).toUpperCase() }}</div>
|
|
45
|
+
<span class="rf-greeting__text">Hola, <strong class="rf-greeting__name">{{ name }}</strong></span>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<!-- Actions -->
|
|
49
|
+
<div class="rf-actions">
|
|
50
|
+
<button class="rf-btn rf-btn--primary" (click)="onPrimary()">
|
|
51
|
+
<svg class="rf-btn__ico" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd"/></svg>
|
|
52
|
+
Acción del MFE
|
|
53
|
+
</button>
|
|
54
|
+
<button class="rf-btn rf-btn--ghost" (click)="onSecondary()">
|
|
55
|
+
<svg class="rf-btn__ico" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/></svg>
|
|
56
|
+
Shadow DOM
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<!-- Footer chips -->
|
|
61
|
+
<div class="rf-chips">
|
|
62
|
+
<span class="rf-chip rf-chip--orange">Angular 21</span>
|
|
63
|
+
<span class="rf-chip rf-chip--muted">Reflow-js</span>
|
|
64
|
+
<span class="rf-chip rf-chip--outline">Shadow DOM</span>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
`,
|
|
69
|
+
styles: [`
|
|
70
|
+
:host {
|
|
71
|
+
--rf-orange: #F87202;
|
|
72
|
+
--rf-dark: #222A2D;
|
|
73
|
+
--rf-light: #E6E7E8;
|
|
74
|
+
--rf-surface: #2A3438;
|
|
75
|
+
--rf-border: rgba(248, 114, 2, 0.18);
|
|
76
|
+
--rf-muted: rgba(230, 231, 232, 0.45);
|
|
77
|
+
|
|
78
|
+
display: flex;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
align-items: center;
|
|
81
|
+
width: 100%;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ── Card shell ── */
|
|
85
|
+
.rf-card {
|
|
86
|
+
position: relative;
|
|
87
|
+
width: 100%;
|
|
88
|
+
max-width: 440px;
|
|
89
|
+
border-radius: 1.25rem;
|
|
90
|
+
background: var(--rf-surface);
|
|
91
|
+
border: 1px solid var(--rf-border);
|
|
92
|
+
box-shadow:
|
|
93
|
+
0 0 0 1px rgba(248,114,2,0.06),
|
|
94
|
+
0 24px 64px rgba(0,0,0,0.55),
|
|
95
|
+
0 4px 16px rgba(0,0,0,0.35);
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', system-ui, sans-serif;
|
|
98
|
+
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
|
99
|
+
}
|
|
100
|
+
.rf-card:hover {
|
|
101
|
+
transform: translateY(-3px);
|
|
102
|
+
box-shadow:
|
|
103
|
+
0 0 0 1px rgba(248,114,2,0.14),
|
|
104
|
+
0 32px 80px rgba(0,0,0,0.6),
|
|
105
|
+
0 8px 24px rgba(248,114,2,0.12);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* ambient orange glow */
|
|
109
|
+
.rf-glow {
|
|
110
|
+
position: absolute;
|
|
111
|
+
top: -60px; left: 50%;
|
|
112
|
+
transform: translateX(-50%);
|
|
113
|
+
width: 260px; height: 120px;
|
|
114
|
+
background: radial-gradient(ellipse, rgba(248,114,2,0.18) 0%, transparent 70%);
|
|
115
|
+
pointer-events: none;
|
|
116
|
+
z-index: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* top accent line */
|
|
120
|
+
.rf-card__top-bar {
|
|
121
|
+
height: 3px;
|
|
122
|
+
background: linear-gradient(90deg, transparent 0%, var(--rf-orange) 40%, #ffaa55 60%, transparent 100%);
|
|
123
|
+
position: relative;
|
|
124
|
+
z-index: 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.rf-card__inner {
|
|
128
|
+
position: relative;
|
|
129
|
+
z-index: 1;
|
|
130
|
+
padding: 2rem 2rem 1.75rem;
|
|
131
|
+
display: flex;
|
|
132
|
+
flex-direction: column;
|
|
133
|
+
gap: 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* ── Header row ── */
|
|
137
|
+
.rf-card__header {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
gap: 0.75rem;
|
|
141
|
+
margin-bottom: 1.5rem;
|
|
142
|
+
}
|
|
143
|
+
.rf-icon-wrap {
|
|
144
|
+
width: 42px; height: 42px;
|
|
145
|
+
border-radius: 0.75rem;
|
|
146
|
+
/* No background/border for SVG, keep demo version */
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
flex-shrink: 0;
|
|
151
|
+
}
|
|
152
|
+
.rf-badge {
|
|
153
|
+
font-size: 0.7rem;
|
|
154
|
+
font-weight: 700;
|
|
155
|
+
letter-spacing: 0.08em;
|
|
156
|
+
text-transform: uppercase;
|
|
157
|
+
color: var(--rf-orange);
|
|
158
|
+
background: rgba(248,114,2,0.10);
|
|
159
|
+
border: 1px solid rgba(248,114,2,0.20);
|
|
160
|
+
padding: 0.22rem 0.7rem;
|
|
161
|
+
border-radius: 999px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ── Typography ── */
|
|
165
|
+
.rf-card__title {
|
|
166
|
+
margin: 0 0 0.6rem;
|
|
167
|
+
font-size: 1.5rem;
|
|
168
|
+
font-weight: 800;
|
|
169
|
+
color: var(--rf-light);
|
|
170
|
+
letter-spacing: -0.03em;
|
|
171
|
+
line-height: 1.2;
|
|
172
|
+
}
|
|
173
|
+
.rf-card__desc {
|
|
174
|
+
margin: 0;
|
|
175
|
+
font-size: 0.875rem;
|
|
176
|
+
color: var(--rf-muted);
|
|
177
|
+
line-height: 1.7;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* ── Divider ── */
|
|
181
|
+
.rf-divider {
|
|
182
|
+
height: 1px;
|
|
183
|
+
background: linear-gradient(90deg, transparent, rgba(230,231,232,0.10), transparent);
|
|
184
|
+
margin: 1.5rem 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* ── Greeting ── */
|
|
188
|
+
.rf-greeting {
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
gap: 0.75rem;
|
|
192
|
+
margin-bottom: 1.75rem;
|
|
193
|
+
}
|
|
194
|
+
.rf-avatar {
|
|
195
|
+
width: 36px; height: 36px;
|
|
196
|
+
border-radius: 50%;
|
|
197
|
+
background: linear-gradient(135deg, var(--rf-orange), #ffaa55);
|
|
198
|
+
color: #fff;
|
|
199
|
+
font-size: 0.875rem;
|
|
200
|
+
font-weight: 800;
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
justify-content: center;
|
|
204
|
+
flex-shrink: 0;
|
|
205
|
+
box-shadow: 0 2px 8px rgba(248,114,2,0.40);
|
|
206
|
+
}
|
|
207
|
+
.rf-greeting__text { font-size: 0.9rem; color: var(--rf-muted); }
|
|
208
|
+
.rf-greeting__name { color: var(--rf-light); font-weight: 700; }
|
|
209
|
+
|
|
210
|
+
/* ── Buttons ── */
|
|
211
|
+
.rf-actions {
|
|
212
|
+
display: flex;
|
|
213
|
+
gap: 0.75rem;
|
|
214
|
+
margin-bottom: 1.75rem;
|
|
215
|
+
flex-wrap: wrap;
|
|
216
|
+
}
|
|
217
|
+
.rf-btn {
|
|
218
|
+
display: inline-flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
gap: 0.45rem;
|
|
221
|
+
padding: 0.6rem 1.25rem;
|
|
222
|
+
border-radius: 0.65rem;
|
|
223
|
+
font-size: 0.85rem;
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
cursor: pointer;
|
|
226
|
+
border: none;
|
|
227
|
+
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
|
|
228
|
+
letter-spacing: 0.01em;
|
|
229
|
+
}
|
|
230
|
+
.rf-btn__ico { width: 14px; height: 14px; flex-shrink: 0; }
|
|
231
|
+
|
|
232
|
+
.rf-btn--primary {
|
|
233
|
+
background: var(--rf-orange);
|
|
234
|
+
color: #fff;
|
|
235
|
+
box-shadow: 0 4px 16px rgba(248,114,2,0.40);
|
|
236
|
+
}
|
|
237
|
+
.rf-btn--primary:hover {
|
|
238
|
+
background: #ff8c1a;
|
|
239
|
+
transform: translateY(-2px);
|
|
240
|
+
box-shadow: 0 8px 24px rgba(248,114,2,0.50);
|
|
241
|
+
}
|
|
242
|
+
.rf-btn--primary:active { transform: translateY(0); }
|
|
243
|
+
|
|
244
|
+
.rf-btn--ghost {
|
|
245
|
+
background: rgba(230,231,232,0.06);
|
|
246
|
+
color: var(--rf-light);
|
|
247
|
+
border: 1px solid rgba(230,231,232,0.14);
|
|
248
|
+
}
|
|
249
|
+
.rf-btn--ghost:hover {
|
|
250
|
+
background: rgba(230,231,232,0.10);
|
|
251
|
+
border-color: rgba(230,231,232,0.24);
|
|
252
|
+
transform: translateY(-2px);
|
|
253
|
+
}
|
|
254
|
+
.rf-btn--ghost:active { transform: translateY(0); }
|
|
255
|
+
|
|
256
|
+
/* ── Chips ── */
|
|
257
|
+
.rf-chips {
|
|
258
|
+
display: flex;
|
|
259
|
+
gap: 0.5rem;
|
|
260
|
+
flex-wrap: wrap;
|
|
261
|
+
}
|
|
262
|
+
.rf-chip {
|
|
263
|
+
display: inline-block;
|
|
264
|
+
padding: 0.18rem 0.6rem;
|
|
265
|
+
border-radius: 999px;
|
|
266
|
+
font-size: 0.68rem;
|
|
267
|
+
font-weight: 600;
|
|
268
|
+
letter-spacing: 0.05em;
|
|
269
|
+
text-transform: uppercase;
|
|
270
|
+
}
|
|
271
|
+
.rf-chip--orange { background: rgba(248,114,2,0.12); color: var(--rf-orange); border: 1px solid rgba(248,114,2,0.25); }
|
|
272
|
+
.rf-chip--muted { background: rgba(230,231,232,0.07); color: var(--rf-muted); border: 1px solid rgba(230,231,232,0.12); }
|
|
273
|
+
.rf-chip--outline { background: transparent; color: rgba(230,231,232,0.35); border: 1px solid rgba(230,231,232,0.12); }
|
|
274
|
+
`]
|
|
275
|
+
})
|
|
276
|
+
export class WelcomeComponent {
|
|
277
|
+
@Input() title: string = 'demo-angular Angular MFE';
|
|
278
|
+
@Input() description: string = 'Componente Angular encapsulado con Shadow DOM por Reflow-js.';
|
|
279
|
+
@Input() name: string = 'Developer';
|
|
280
|
+
|
|
281
|
+
onPrimary() { console.log('[Reflow] welcome-card primary action'); }
|
|
282
|
+
onSecondary() { alert(`¡Shadow DOM activo! Estilos de '${this.title}' están encapsulados.`); }
|
|
283
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Angular MFE entry point — Reflow-js encapsulation
|
|
2
|
+
import 'zone.js';
|
|
3
|
+
import '@angular/compiler'; // Required for JIT compilation outside Angular CLI
|
|
4
|
+
import { Reflow } from '@reflow-js/core';
|
|
5
|
+
import { WelcomeComponent } from './components/welcome.component';
|
|
6
|
+
import './styles.css';
|
|
7
|
+
|
|
8
|
+
// Import Angular bootstrapping APIs and pass them to the Reflow IoC container.
|
|
9
|
+
// createApplication: creates an isolated Angular environment (no global document.querySelector needed)
|
|
10
|
+
// createComponent: mounts a component onto an explicit hostElement — works inside Shadow DOM
|
|
11
|
+
import { createApplication, bootstrapApplication } from '@angular/platform-browser';
|
|
12
|
+
import { createComponent } from '@angular/core';
|
|
13
|
+
|
|
14
|
+
Reflow.setDependency('@angular/platform-browser', { createApplication, bootstrapApplication });
|
|
15
|
+
Reflow.setDependency('@angular/core', { createComponent });
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reflow.init() inicializa el sistema y carga application.json.
|
|
19
|
+
* Una vez listo, Reflow.define() registra el componente Angular
|
|
20
|
+
* como un Custom Element nativo con Shadow DOM.
|
|
21
|
+
*
|
|
22
|
+
* Patrón idéntico al de React, pero framework: 'angular'
|
|
23
|
+
*/
|
|
24
|
+
Reflow.init({
|
|
25
|
+
propertiesFile: 'application.json',
|
|
26
|
+
}).then(() => {
|
|
27
|
+
console.log('[Reflow] ✅ Inicializado — Angular MFE');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reflow.define(tagName, Component, defaultProps, options)
|
|
31
|
+
* framework: 'angular' → activa el bridge Angular de ReflowElement
|
|
32
|
+
* shadow: true → Shadow DOM activo, estilos encapsulados
|
|
33
|
+
*/
|
|
34
|
+
Reflow.define(
|
|
35
|
+
'welcome-card',
|
|
36
|
+
WelcomeComponent,
|
|
37
|
+
{ title: '{{name}} Angular MFE', description: 'Componente Angular encapsulado con Shadow DOM.' },
|
|
38
|
+
{ shadow: true, framework: 'angular' }
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
console.log('[Reflow] 🧩 welcome-card registrado como Web Component (Angular)');
|
|
42
|
+
}).catch((e: any) => {
|
|
43
|
+
console.error('[Reflow] ❌ Error en inicialización', e);
|
|
44
|
+
});
|