@lit-roa/pasos 0.0.0-stage → 0.1.2

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 CHANGED
@@ -1,3 +1,34 @@
1
- # Temporary Holding Version
1
+ # @lit-roa/pasos
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Pasos del checkout (stepper). Los pasos ya hechos llevan palomita y se
4
+ pueden tocar para regresar. Si el componente mide menos de 520 px, muestra
5
+ "Envío · Paso 3 de 4" con barra de avance y una liga al paso anterior.
6
+
7
+ ```bash
8
+ npm install @lit-roa/pasos
9
+ ```
10
+
11
+ ```html
12
+ <lr-pasos actual="1" pasos='["Carrito","Datos","Envío","Pago"]'></lr-pasos>
13
+ <script>
14
+ const pasos = document.querySelector('lr-pasos');
15
+ pasos.addEventListener('cambio-paso', (e) => (pasos.actual = e.detail.indice));
16
+ </script>
17
+ ```
18
+
19
+ El componente no cambia `actual` por su cuenta: avisa con `cambio-paso` y
20
+ tu sitio decide (por ejemplo, si los datos del paso actual son válidos).
21
+
22
+ | Atributo | Tipo | Por omisión |
23
+ | --- | --- | --- |
24
+ | `pasos` | `(string \| { titulo, descripcion? })[]` (JSON en atributo) | `[]` |
25
+ | `actual` | `number` (desde 0) | `0` |
26
+ | `sin-regresar` | `boolean` | `false` |
27
+
28
+ Evento `cambio-paso` con `{ indice, paso }`.
29
+
30
+ ## Desarrollo
31
+
32
+ ```bash
33
+ npm run dev -w @lit-roa/pasos
34
+ ```
@@ -0,0 +1,2 @@
1
+ export { LrPasos, type DetalleCambioPaso, type Paso } from './pasos.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { LrPasos } from './pasos.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAqC,MAAM,YAAY,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { LitElement } from 'lit';
2
+ export interface Paso {
3
+ titulo: string;
4
+ descripcion?: string;
5
+ }
6
+ export interface DetalleCambioPaso {
7
+ indice: number;
8
+ paso: Paso;
9
+ }
10
+ /**
11
+ * Pasos del checkout (stepper). Los pasos anteriores al actual quedan
12
+ * marcados y se pueden tocar para regresar. En pantallas angostas muestra
13
+ * "Paso 2 de 4" con una barra de avance.
14
+ *
15
+ * @fires cambio-paso - Al tocar un paso ya hecho. `detail`: `{ indice, paso }`.
16
+ * El componente no cambia `actual`: lo hace el sitio.
17
+ */
18
+ export declare class LrPasos extends LitElement {
19
+ static styles: import("lit").CSSResult[];
20
+ /** Pasos: textos o `{ titulo, descripcion }`. Como atributo, en JSON. */
21
+ pasos: (Paso | string)[];
22
+ /** Índice del paso actual, desde 0. */
23
+ actual: number;
24
+ /** Los pasos hechos no se pueden tocar. */
25
+ sinRegresar: boolean;
26
+ render(): import("lit-html").TemplateResult<1>;
27
+ private elegir;
28
+ }
29
+ declare global {
30
+ interface HTMLElementTagNameMap {
31
+ 'lr-pasos': LrPasos;
32
+ }
33
+ }
34
+ //# sourceMappingURL=pasos.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pasos.d.ts","sourceRoot":"","sources":["../src/pasos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAIrD,MAAM,WAAW,IAAI;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;;;;;GAOG;AACH,qBACa,OAAQ,SAAQ,UAAU;IACrC,OAAgB,MAAM,4BAiJpB;IAEF,yEAAyE;IAC9C,KAAK,EAAE,CAAC,IAAI,GAAG,MAAM,CAAC,EAAE,CAAM;IAEzD,uCAAuC;IACX,MAAM,SAAK;IAEvC,2CAA2C;IACa,WAAW,UAAS;IAEnE,MAAM;IAqCf,OAAO,CAAC,MAAM;CAGf;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,UAAU,EAAE,OAAO,CAAC;KACrB;CACF"}
package/dist/pasos.js ADDED
@@ -0,0 +1,227 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html, nothing } from 'lit';
8
+ import { customElement, property } from 'lit/decorators.js';
9
+ import { emitir, estilosBase, iconoCheck, tokens } from '@lit-roa/base';
10
+ /**
11
+ * Pasos del checkout (stepper). Los pasos anteriores al actual quedan
12
+ * marcados y se pueden tocar para regresar. En pantallas angostas muestra
13
+ * "Paso 2 de 4" con una barra de avance.
14
+ *
15
+ * @fires cambio-paso - Al tocar un paso ya hecho. `detail`: `{ indice, paso }`.
16
+ * El componente no cambia `actual`: lo hace el sitio.
17
+ */
18
+ let LrPasos = class LrPasos extends LitElement {
19
+ constructor() {
20
+ super(...arguments);
21
+ /** Pasos: textos o `{ titulo, descripcion }`. Como atributo, en JSON. */
22
+ this.pasos = [];
23
+ /** Índice del paso actual, desde 0. */
24
+ this.actual = 0;
25
+ /** Los pasos hechos no se pueden tocar. */
26
+ this.sinRegresar = false;
27
+ }
28
+ static { this.styles = [
29
+ tokens,
30
+ estilosBase,
31
+ css `
32
+ :host {
33
+ display: block;
34
+ container-type: inline-size;
35
+ }
36
+ ol {
37
+ display: flex;
38
+ margin: 0;
39
+ padding: 0;
40
+ list-style: none;
41
+ }
42
+ li {
43
+ flex: 1;
44
+ position: relative;
45
+ display: flex;
46
+ flex-direction: column;
47
+ align-items: center;
48
+ text-align: center;
49
+ min-width: 0;
50
+ }
51
+ li:not(:first-child)::before {
52
+ content: '';
53
+ position: absolute;
54
+ top: 1rem;
55
+ right: calc(50% + 1.25rem);
56
+ left: calc(-50% + 1.25rem);
57
+ height: 2px;
58
+ background: var(--_color-borde);
59
+ }
60
+ li.hecho::before,
61
+ li.actual::before {
62
+ background: var(--_color-primario);
63
+ }
64
+ .marca {
65
+ all: unset;
66
+ box-sizing: border-box;
67
+ display: flex;
68
+ flex-direction: column;
69
+ align-items: center;
70
+ gap: 0.5rem;
71
+ max-width: 100%;
72
+ }
73
+ button.marca {
74
+ cursor: pointer;
75
+ }
76
+ button.marca:hover .titulo {
77
+ text-decoration: underline;
78
+ }
79
+ button.marca:focus-visible .circulo {
80
+ outline: 2px solid var(--_color-primario);
81
+ outline-offset: 2px;
82
+ }
83
+ .circulo {
84
+ display: grid;
85
+ place-items: center;
86
+ width: 2rem;
87
+ height: 2rem;
88
+ border-radius: 999px;
89
+ border: 2px solid var(--_color-borde);
90
+ background: var(--_color-fondo);
91
+ color: var(--_color-texto-suave);
92
+ font-size: 0.875rem;
93
+ font-weight: 700;
94
+ }
95
+ .hecho .circulo {
96
+ border-color: var(--_color-primario);
97
+ background: var(--_color-primario);
98
+ color: var(--_color-sobre-primario);
99
+ font-size: 1rem;
100
+ }
101
+ .actual .circulo {
102
+ border-color: var(--_color-primario);
103
+ color: var(--_color-primario);
104
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--_color-primario) 18%, transparent);
105
+ }
106
+ .titulo {
107
+ font-size: 0.875rem;
108
+ font-weight: 600;
109
+ color: var(--_color-texto-suave);
110
+ overflow: hidden;
111
+ text-overflow: ellipsis;
112
+ max-width: 100%;
113
+ }
114
+ .hecho .titulo,
115
+ .actual .titulo {
116
+ color: var(--_color-texto);
117
+ }
118
+ .descripcion {
119
+ font-size: 0.75rem;
120
+ color: var(--_color-texto-suave);
121
+ margin-top: -0.25rem;
122
+ }
123
+ .compacto {
124
+ display: none;
125
+ }
126
+ @container (max-width: 520px) {
127
+ ol {
128
+ display: none;
129
+ }
130
+ .compacto {
131
+ display: grid;
132
+ gap: 0.5rem;
133
+ }
134
+ }
135
+ .compacto .arriba {
136
+ display: flex;
137
+ justify-content: space-between;
138
+ align-items: baseline;
139
+ gap: 1rem;
140
+ font-size: 0.875rem;
141
+ }
142
+ .compacto strong {
143
+ font-size: 1rem;
144
+ }
145
+ .compacto span {
146
+ color: var(--_color-texto-suave);
147
+ white-space: nowrap;
148
+ }
149
+ .barra {
150
+ height: 6px;
151
+ border-radius: 999px;
152
+ background: var(--_color-borde);
153
+ overflow: hidden;
154
+ }
155
+ .barra i {
156
+ display: block;
157
+ height: 100%;
158
+ border-radius: inherit;
159
+ background: var(--_color-primario);
160
+ transition: width 0.3s;
161
+ }
162
+ .regresar {
163
+ all: unset;
164
+ justify-self: start;
165
+ font-size: 0.8125rem;
166
+ color: var(--_color-primario);
167
+ cursor: pointer;
168
+ }
169
+ .regresar:focus-visible {
170
+ outline: 2px solid var(--_color-primario);
171
+ }
172
+ `,
173
+ ]; }
174
+ render() {
175
+ const pasos = this.pasos.map((p) => (typeof p === 'string' ? { titulo: p } : p));
176
+ const actual = Math.min(Math.max(0, this.actual), pasos.length - 1);
177
+ const anterior = pasos[actual - 1];
178
+ return html `
179
+ <nav aria-label="Pasos de la compra">
180
+ <ol>
181
+ ${pasos.map((p, i) => {
182
+ const estado = i < actual ? 'hecho' : i === actual ? 'actual' : 'pendiente';
183
+ const contenido = html `<span class="circulo">${estado === 'hecho' ? iconoCheck : i + 1}</span>
184
+ <span class="titulo">${p.titulo}</span>
185
+ ${p.descripcion ? html `<span class="descripcion">${p.descripcion}</span>` : nothing}`;
186
+ return html `<li class=${estado} aria-current=${estado === 'actual' ? 'step' : nothing}>
187
+ ${estado === 'hecho' && !this.sinRegresar
188
+ ? html `<button class="marca" aria-label="${p.titulo} (hecho), regresar" @click=${() => this.elegir(i, p)}>
189
+ ${contenido}
190
+ </button>`
191
+ : html `<span class="marca">${contenido}</span>`}
192
+ </li>`;
193
+ })}
194
+ </ol>
195
+ <div class="compacto">
196
+ <div class="arriba">
197
+ <strong>${pasos[actual]?.titulo ?? ''}</strong>
198
+ <span>Paso ${actual + 1} de ${pasos.length}</span>
199
+ </div>
200
+ <div class="barra"><i style="width: ${((actual + 1) / Math.max(1, pasos.length)) * 100}%"></i></div>
201
+ ${anterior && !this.sinRegresar
202
+ ? html `<button class="regresar" @click=${() => this.elegir(actual - 1, anterior)}>
203
+ ← ${anterior.titulo}
204
+ </button>`
205
+ : nothing}
206
+ </div>
207
+ </nav>
208
+ `;
209
+ }
210
+ elegir(indice, paso) {
211
+ emitir(this, 'cambio-paso', { indice, paso });
212
+ }
213
+ };
214
+ __decorate([
215
+ property({ type: Array })
216
+ ], LrPasos.prototype, "pasos", void 0);
217
+ __decorate([
218
+ property({ type: Number })
219
+ ], LrPasos.prototype, "actual", void 0);
220
+ __decorate([
221
+ property({ type: Boolean, attribute: 'sin-regresar' })
222
+ ], LrPasos.prototype, "sinRegresar", void 0);
223
+ LrPasos = __decorate([
224
+ customElement('lr-pasos')
225
+ ], LrPasos);
226
+ export { LrPasos };
227
+ //# sourceMappingURL=pasos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pasos.js","sourceRoot":"","sources":["../src/pasos.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "name": "@lit-roa/pasos",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.2",
4
+ "description": "Pasos del checkout (stepper) responsive.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "sideEffects": [
19
+ "./dist/index.js"
20
+ ],
21
+ "scripts": {
22
+ "dev": "vite",
23
+ "build": "tsc -b"
24
+ },
25
+ "dependencies": {
26
+ "@lit-roa/base": "^0.5.0",
27
+ "lit": "^3.3.3"
28
+ },
29
+ "license": "MIT",
30
+ "devDependencies": {
31
+ "@lit-roa/boton": "^0.1.3"
32
+ }
33
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { LrPasos, type DetalleCambioPaso, type Paso } from './pasos.js';
package/src/pasos.ts ADDED
@@ -0,0 +1,227 @@
1
+ import { LitElement, css, html, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { emitir, estilosBase, iconoCheck, tokens } from '@lit-roa/base';
4
+
5
+ export interface Paso {
6
+ titulo: string;
7
+ descripcion?: string;
8
+ }
9
+
10
+ export interface DetalleCambioPaso {
11
+ indice: number;
12
+ paso: Paso;
13
+ }
14
+
15
+ /**
16
+ * Pasos del checkout (stepper). Los pasos anteriores al actual quedan
17
+ * marcados y se pueden tocar para regresar. En pantallas angostas muestra
18
+ * "Paso 2 de 4" con una barra de avance.
19
+ *
20
+ * @fires cambio-paso - Al tocar un paso ya hecho. `detail`: `{ indice, paso }`.
21
+ * El componente no cambia `actual`: lo hace el sitio.
22
+ */
23
+ @customElement('lr-pasos')
24
+ export class LrPasos extends LitElement {
25
+ static override styles = [
26
+ tokens,
27
+ estilosBase,
28
+ css`
29
+ :host {
30
+ display: block;
31
+ container-type: inline-size;
32
+ }
33
+ ol {
34
+ display: flex;
35
+ margin: 0;
36
+ padding: 0;
37
+ list-style: none;
38
+ }
39
+ li {
40
+ flex: 1;
41
+ position: relative;
42
+ display: flex;
43
+ flex-direction: column;
44
+ align-items: center;
45
+ text-align: center;
46
+ min-width: 0;
47
+ }
48
+ li:not(:first-child)::before {
49
+ content: '';
50
+ position: absolute;
51
+ top: 1rem;
52
+ right: calc(50% + 1.25rem);
53
+ left: calc(-50% + 1.25rem);
54
+ height: 2px;
55
+ background: var(--_color-borde);
56
+ }
57
+ li.hecho::before,
58
+ li.actual::before {
59
+ background: var(--_color-primario);
60
+ }
61
+ .marca {
62
+ all: unset;
63
+ box-sizing: border-box;
64
+ display: flex;
65
+ flex-direction: column;
66
+ align-items: center;
67
+ gap: 0.5rem;
68
+ max-width: 100%;
69
+ }
70
+ button.marca {
71
+ cursor: pointer;
72
+ }
73
+ button.marca:hover .titulo {
74
+ text-decoration: underline;
75
+ }
76
+ button.marca:focus-visible .circulo {
77
+ outline: 2px solid var(--_color-primario);
78
+ outline-offset: 2px;
79
+ }
80
+ .circulo {
81
+ display: grid;
82
+ place-items: center;
83
+ width: 2rem;
84
+ height: 2rem;
85
+ border-radius: 999px;
86
+ border: 2px solid var(--_color-borde);
87
+ background: var(--_color-fondo);
88
+ color: var(--_color-texto-suave);
89
+ font-size: 0.875rem;
90
+ font-weight: 700;
91
+ }
92
+ .hecho .circulo {
93
+ border-color: var(--_color-primario);
94
+ background: var(--_color-primario);
95
+ color: var(--_color-sobre-primario);
96
+ font-size: 1rem;
97
+ }
98
+ .actual .circulo {
99
+ border-color: var(--_color-primario);
100
+ color: var(--_color-primario);
101
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--_color-primario) 18%, transparent);
102
+ }
103
+ .titulo {
104
+ font-size: 0.875rem;
105
+ font-weight: 600;
106
+ color: var(--_color-texto-suave);
107
+ overflow: hidden;
108
+ text-overflow: ellipsis;
109
+ max-width: 100%;
110
+ }
111
+ .hecho .titulo,
112
+ .actual .titulo {
113
+ color: var(--_color-texto);
114
+ }
115
+ .descripcion {
116
+ font-size: 0.75rem;
117
+ color: var(--_color-texto-suave);
118
+ margin-top: -0.25rem;
119
+ }
120
+ .compacto {
121
+ display: none;
122
+ }
123
+ @container (max-width: 520px) {
124
+ ol {
125
+ display: none;
126
+ }
127
+ .compacto {
128
+ display: grid;
129
+ gap: 0.5rem;
130
+ }
131
+ }
132
+ .compacto .arriba {
133
+ display: flex;
134
+ justify-content: space-between;
135
+ align-items: baseline;
136
+ gap: 1rem;
137
+ font-size: 0.875rem;
138
+ }
139
+ .compacto strong {
140
+ font-size: 1rem;
141
+ }
142
+ .compacto span {
143
+ color: var(--_color-texto-suave);
144
+ white-space: nowrap;
145
+ }
146
+ .barra {
147
+ height: 6px;
148
+ border-radius: 999px;
149
+ background: var(--_color-borde);
150
+ overflow: hidden;
151
+ }
152
+ .barra i {
153
+ display: block;
154
+ height: 100%;
155
+ border-radius: inherit;
156
+ background: var(--_color-primario);
157
+ transition: width 0.3s;
158
+ }
159
+ .regresar {
160
+ all: unset;
161
+ justify-self: start;
162
+ font-size: 0.8125rem;
163
+ color: var(--_color-primario);
164
+ cursor: pointer;
165
+ }
166
+ .regresar:focus-visible {
167
+ outline: 2px solid var(--_color-primario);
168
+ }
169
+ `,
170
+ ];
171
+
172
+ /** Pasos: textos o `{ titulo, descripcion }`. Como atributo, en JSON. */
173
+ @property({ type: Array }) pasos: (Paso | string)[] = [];
174
+
175
+ /** Índice del paso actual, desde 0. */
176
+ @property({ type: Number }) actual = 0;
177
+
178
+ /** Los pasos hechos no se pueden tocar. */
179
+ @property({ type: Boolean, attribute: 'sin-regresar' }) sinRegresar = false;
180
+
181
+ override render() {
182
+ const pasos = this.pasos.map((p) => (typeof p === 'string' ? { titulo: p } : p));
183
+ const actual = Math.min(Math.max(0, this.actual), pasos.length - 1);
184
+ const anterior = pasos[actual - 1];
185
+ return html`
186
+ <nav aria-label="Pasos de la compra">
187
+ <ol>
188
+ ${pasos.map((p, i) => {
189
+ const estado = i < actual ? 'hecho' : i === actual ? 'actual' : 'pendiente';
190
+ const contenido = html`<span class="circulo">${estado === 'hecho' ? iconoCheck : i + 1}</span>
191
+ <span class="titulo">${p.titulo}</span>
192
+ ${p.descripcion ? html`<span class="descripcion">${p.descripcion}</span>` : nothing}`;
193
+ return html`<li class=${estado} aria-current=${estado === 'actual' ? 'step' : nothing}>
194
+ ${estado === 'hecho' && !this.sinRegresar
195
+ ? html`<button class="marca" aria-label="${p.titulo} (hecho), regresar" @click=${() => this.elegir(i, p)}>
196
+ ${contenido}
197
+ </button>`
198
+ : html`<span class="marca">${contenido}</span>`}
199
+ </li>`;
200
+ })}
201
+ </ol>
202
+ <div class="compacto">
203
+ <div class="arriba">
204
+ <strong>${pasos[actual]?.titulo ?? ''}</strong>
205
+ <span>Paso ${actual + 1} de ${pasos.length}</span>
206
+ </div>
207
+ <div class="barra"><i style="width: ${((actual + 1) / Math.max(1, pasos.length)) * 100}%"></i></div>
208
+ ${anterior && !this.sinRegresar
209
+ ? html`<button class="regresar" @click=${() => this.elegir(actual - 1, anterior)}>
210
+ ← ${anterior.titulo}
211
+ </button>`
212
+ : nothing}
213
+ </div>
214
+ </nav>
215
+ `;
216
+ }
217
+
218
+ private elegir(indice: number, paso: Paso) {
219
+ emitir<DetalleCambioPaso>(this, 'cambio-paso', { indice, paso });
220
+ }
221
+ }
222
+
223
+ declare global {
224
+ interface HTMLElementTagNameMap {
225
+ 'lr-pasos': LrPasos;
226
+ }
227
+ }