@erplora/outfitkit 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import './ok-cta-band.js';
@@ -0,0 +1 @@
1
+ import './ok-data-table.js';
@@ -56,8 +56,9 @@ export interface DataTablePrimaryAction {
56
56
  }
57
57
  /** Clave estable de fila: nombre de campo o función que la devuelve. */
58
58
  export type DataTableRowKey = string | ((row: Record<string, unknown>) => string);
59
- /** (NUEVO, additivo) Todos los textos humanos del data-table, para i18n. Default en INGLÉS.
60
- * Se pasan desde fuera vía la prop `.labels` (parcial); lo no pasado cae al default inglés.
59
+ /** (NUEVO, additivo) Todos los textos humanos del data-table, para i18n.
60
+ * Se pasan desde fuera vía la prop `.labels` (parcial); lo no pasado cae al idioma del documento
61
+ * (`<html lang="es">` → español; cualquier otro idioma → inglés).
61
62
  * El contenido data-driven (columns/rows/options) NO va aquí — ya es externo. */
62
63
  export interface OkDataTableLabels {
63
64
  /** Placeholder del buscador. */
@@ -234,6 +235,9 @@ export declare class OkDataTable extends LitElement {
234
235
  private internalSelection;
235
236
  private menuOpen;
236
237
  private menuEv?;
238
+ private readonly onLocaleChanged;
239
+ connectedCallback(): void;
240
+ disconnectedCallback(): void;
237
241
  private get t();
238
242
  /** Placeholder efectivo del buscador (prop explícita → label i18n → default inglés). */
239
243
  private get effSearchPlaceholder();
package/dist/layout.css CHANGED
@@ -205,6 +205,141 @@
205
205
  color: var(--ok-muted, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.62));
206
206
  }
207
207
 
208
+ /* ===== Editorial content and documentation surfaces =====
209
+ *
210
+ * Reusable public/documentation primitives: constrained readable width,
211
+ * Ionic search/accordion theming and rich-text content generated from Markdown.
212
+ */
213
+ ion-searchbar.ok-search {
214
+ --background: var(--ok-surface, var(--ion-card-background, #fff));
215
+ --border-radius: 0.875rem;
216
+ --box-shadow: 0 10px 28px -22px color-mix(in oklab, var(--ok-text, var(--ion-text-color, #18181b)) 45%, transparent);
217
+ --color: var(--ok-text, var(--ion-text-color, #18181b));
218
+ --icon-color: var(--ok-primary, var(--ion-color-primary, #1496d6));
219
+ --placeholder-color: var(--ok-text, var(--ion-text-color, #18181b));
220
+ --placeholder-opacity: 0.55;
221
+ padding-inline: 0;
222
+ margin-block-end: 1rem;
223
+ }
224
+
225
+ ion-accordion.ok-accordion {
226
+ --background: var(--ok-surface, var(--ion-card-background, var(--ion-background-color, #fff)));
227
+ }
228
+
229
+ ion-item.ok-accordion-header {
230
+ --background: var(--ok-surface, var(--ion-card-background, var(--ion-background-color, #fff)));
231
+ --color: var(--ok-text, var(--ion-text-color, #18181b));
232
+ --padding-start: 1.125rem;
233
+ --inner-padding-end: 1rem;
234
+ font-weight: 600;
235
+ }
236
+
237
+ ion-item.ok-accordion-header iconify-icon {
238
+ color: var(--ok-primary, var(--ion-color-primary, #1496d6));
239
+ margin-inline-end: 0.75rem;
240
+ }
241
+
242
+ .ok-accordion-body {
243
+ padding: 0.25rem 1.25rem 1.5rem;
244
+ background: var(--ok-surface, var(--ion-card-background, var(--ion-background-color, #fff)));
245
+ }
246
+
247
+ .ok-prose {
248
+ color: var(--ok-text, var(--ion-text-color, #18181b));
249
+ line-height: 1.7;
250
+ }
251
+
252
+ .ok-prose h2 {
253
+ margin: 1.5rem 0 0.75rem;
254
+ font-family: var(--ok-font-display, var(--ok-font, system-ui, sans-serif));
255
+ font-size: 1.25rem;
256
+ font-weight: 600;
257
+ }
258
+
259
+ .ok-prose h3 {
260
+ margin: 1.25rem 0 0.5rem;
261
+ font-family: var(--ok-font-display, var(--ok-font, system-ui, sans-serif));
262
+ font-size: 1.1rem;
263
+ font-weight: 600;
264
+ }
265
+
266
+ .ok-prose p {
267
+ margin: 0.5rem 0;
268
+ }
269
+
270
+ .ok-prose :is(ul, ol) {
271
+ margin: 0.5rem 0;
272
+ padding-inline-start: 1.5rem;
273
+ }
274
+
275
+ .ok-prose ul {
276
+ list-style: disc;
277
+ }
278
+
279
+ .ok-prose ol {
280
+ list-style: decimal;
281
+ }
282
+
283
+ .ok-prose li {
284
+ margin: 0.25rem 0;
285
+ line-height: 1.6;
286
+ }
287
+
288
+ .ok-prose table {
289
+ width: 100%;
290
+ margin: 1rem 0;
291
+ border-collapse: collapse;
292
+ font-size: 0.9rem;
293
+ }
294
+
295
+ .ok-prose :is(th, td) {
296
+ padding: 0.5rem 0.75rem;
297
+ border: 1px solid color-mix(in oklab, var(--ok-text, var(--ion-text-color, #18181b)) 12%, transparent);
298
+ text-align: start;
299
+ }
300
+
301
+ .ok-prose th {
302
+ background: color-mix(in oklab, var(--ok-text, var(--ion-text-color, #18181b)) 5%, transparent);
303
+ font-weight: 600;
304
+ }
305
+
306
+ .ok-prose code {
307
+ padding: 0.15rem 0.4rem;
308
+ border-radius: 0.25rem;
309
+ background: color-mix(in oklab, var(--ok-text, var(--ion-text-color, #18181b)) 6%, transparent);
310
+ font-size: 0.875em;
311
+ }
312
+
313
+ .ok-prose pre {
314
+ overflow-x: auto;
315
+ margin: 1rem 0;
316
+ padding: 1rem;
317
+ border-radius: 0.5rem;
318
+ background: color-mix(in oklab, var(--ok-text, var(--ion-text-color, #18181b)) 6%, transparent);
319
+ }
320
+
321
+ .ok-prose pre code {
322
+ padding: 0;
323
+ background: none;
324
+ }
325
+
326
+ .ok-prose blockquote {
327
+ margin: 1rem 0;
328
+ padding: 0.5rem 1rem;
329
+ border-inline-start: 3px solid var(--ok-primary, var(--ion-color-primary, #1496d6));
330
+ border-radius: 0 0.5rem 0.5rem 0;
331
+ background: color-mix(in oklab, var(--ok-primary, var(--ion-color-primary, #1496d6)) 5%, transparent);
332
+ }
333
+
334
+ .ok-prose strong {
335
+ font-weight: 600;
336
+ }
337
+
338
+ .ok-prose hr {
339
+ margin: 1.5rem 0;
340
+ border: 0;
341
+ border-top: 1px solid color-mix(in oklab, var(--ok-text, var(--ion-text-color, #18181b)) 12%, transparent);
342
+ }
208
343
  /* Tipografía editorial reutilizable fuera de los componentes. El display se
209
344
  * reserva para el hero principal; page-title para cabeceras interiores e
210
345
  * intro/body para el texto que las acompaña. */
@@ -244,7 +379,6 @@
244
379
  font-size: var(--ok-type-body-size, 1rem);
245
380
  line-height: var(--ok-type-body-leading, 1.6);
246
381
  }
247
-
248
382
  /* ===== Tabla apilable (.ok-table-stack) — patrón «no more tables» =====
249
383
  *
250
384
  * Solo CSS, sin web component (para CRUDs ricos usa <ok-data-table>; esto es
@@ -20,12 +20,29 @@ class OkCtaBand extends LitElement {
20
20
  :host {
21
21
  display: block;
22
22
  width: 100%;
23
- --primary: var(--ion-color-primary, #1496d6);
24
- --primary-contrast: var(--ion-color-primary-contrast, #ffffff);
23
+ --primary: var(--ok-primary, var(--ion-color-primary, #1496d6));
24
+ --primary-contrast: var(--ok-primary-contrast, var(--ion-color-primary-contrast, #ffffff));
25
25
  --color: var(--ok-text, var(--ion-text-color, #18181b));
26
26
  --muted: var(--ok-muted, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.62));
27
27
  --surface: var(--ok-surface, var(--ion-card-background, var(--ion-background-color, #fff)));
28
28
  --radius: var(--ok-radius-lg, 28px);
29
+ --action-outline-color: var(--ok-cta-action-outline-color, var(--primary-contrast));
30
+ --action-outline-border-color: var(
31
+ --ok-cta-action-outline-border-color,
32
+ color-mix(in oklab, var(--action-outline-color) 62%, transparent)
33
+ );
34
+ --action-outline-background-hover: var(
35
+ --ok-cta-action-outline-background-hover,
36
+ color-mix(in oklab, var(--action-outline-color) 12%, transparent)
37
+ );
38
+ --action-outline-background-focused: var(
39
+ --ok-cta-action-outline-background-focused,
40
+ color-mix(in oklab, var(--action-outline-color) 16%, transparent)
41
+ );
42
+ --action-outline-background-activated: var(
43
+ --ok-cta-action-outline-background-activated,
44
+ color-mix(in oklab, var(--action-outline-color) 20%, transparent)
45
+ );
29
46
  font-family: var(--ok-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
30
47
  }
31
48
  .band {
@@ -106,6 +123,18 @@ class OkCtaBand extends LitElement {
106
123
  justify-content: center;
107
124
  margin-top: 0.6rem;
108
125
  }
126
+ :host(:not([variant])) ::slotted(ion-button[fill='outline']),
127
+ :host([variant='solid']) ::slotted(ion-button[fill='outline']) {
128
+ --color: var(--action-outline-color);
129
+ --color-hover: var(--action-outline-color);
130
+ --color-focused: var(--action-outline-color);
131
+ --color-activated: var(--action-outline-color);
132
+ --border-color: var(--action-outline-border-color);
133
+ --background-hover: var(--action-outline-background-hover);
134
+ --background-focused: var(--action-outline-background-focused);
135
+ --background-activated: var(--action-outline-background-activated);
136
+ --ripple-color: var(--action-outline-color);
137
+ }
109
138
  `;
110
139
  }
111
140
  render() {
@@ -59,6 +59,42 @@ const DEFAULT_LABELS = {
59
59
  recordSingular: "record",
60
60
  recordPlural: "records"
61
61
  };
62
+ const ES_LABELS = {
63
+ search: "Buscar…",
64
+ empty: "Sin resultados",
65
+ filters: "Filtros",
66
+ clear: "Limpiar",
67
+ apply: "Aplicar",
68
+ selected: "{n} seleccionados",
69
+ importCsv: "Importar CSV",
70
+ exportCsv: "Exportar CSV",
71
+ add: "Añadir",
72
+ moreActions: "Más acciones",
73
+ rowsPerPage: "Filas por página",
74
+ perPageShort: "{n} / pág.",
75
+ viewList: "Vista lista",
76
+ viewCards: "Vista tarjetas",
77
+ columnsVisible: "Columnas visibles",
78
+ columns: "Columnas",
79
+ actions: "Acciones",
80
+ close: "Cerrar",
81
+ newRecord: "Nuevo",
82
+ form: "Formulario",
83
+ filterPlaceholder: "Filtrar…",
84
+ from: "Desde",
85
+ to: "Hasta",
86
+ fromOf: "{label} desde",
87
+ toOf: "{label} hasta",
88
+ gte: "≥",
89
+ lte: "≤",
90
+ noValues: "Sin valores",
91
+ selectAll: "Seleccionar todo",
92
+ selectRow: "Seleccionar fila",
93
+ select: "Seleccionar",
94
+ showing: "Mostrando {from}–{to} de",
95
+ recordSingular: "registro",
96
+ recordPlural: "registros"
97
+ };
62
98
  class OkDataTable extends LitElement {
63
99
  constructor() {
64
100
  super(...arguments);
@@ -100,6 +136,7 @@ class OkDataTable extends LitElement {
100
136
  this.hiddenKeys = /* @__PURE__ */ new Set();
101
137
  this.internalSelection = /* @__PURE__ */ new Set();
102
138
  this.menuOpen = false;
139
+ this.onLocaleChanged = () => this.requestUpdate();
103
140
  this.onSearch = (ev) => {
104
141
  const value = ev.target.value ?? "";
105
142
  if (this.serverSide) {
@@ -315,6 +352,13 @@ class OkDataTable extends LitElement {
315
352
  .empty .empty-ic { display: grid; place-items: center; width: 3.25rem; height: 3.25rem; border-radius: 999px; background: var(--header-background); font-size: 26px; }
316
353
 
317
354
  .actions { display: flex; gap: 0.25rem; justify-content: flex-end; }
355
+ /* Las acciones de fila son icon-only y de tamaño small en escritorio. En tablet/móvil se
356
+ * amplía el host completo (no solo el icono) para que el área táctil alcance 44×44 px. */
357
+ @media (pointer: coarse), (max-width: 834px) {
358
+ .actions ion-button { min-width: 44px; min-height: 44px; margin: 0; }
359
+ .toolbtn { width: 44px; height: 44px; }
360
+ .pager .nav ion-button { min-width: 44px; min-height: 44px; margin: 0; }
361
+ }
318
362
  /* Spinner de acción en curso (loading): contenido dentro del ion-button small (Ionic lo fija
319
363
  * a 28px en el :host, por eso width/height y no font-size). Cubre tabla y tarjetas: los
320
364
  * botones de fila siempre van dentro de .actions. */
@@ -335,9 +379,22 @@ class OkDataTable extends LitElement {
335
379
  ion-button { --box-shadow: none; }
336
380
  `;
337
381
  }
338
- // ── i18n: textos efectivos (default inglés ← overrides de `.labels`) ──────────────────────
382
+ connectedCallback() {
383
+ super.connectedCallback();
384
+ if (typeof window !== "undefined") {
385
+ window.addEventListener("erplora:locale-changed", this.onLocaleChanged);
386
+ }
387
+ }
388
+ disconnectedCallback() {
389
+ if (typeof window !== "undefined") {
390
+ window.removeEventListener("erplora:locale-changed", this.onLocaleChanged);
391
+ }
392
+ super.disconnectedCallback();
393
+ }
394
+ // ── i18n: idioma del documento ← overrides explícitos de `.labels` ─────────────────────────
339
395
  get t() {
340
- return { ...DEFAULT_LABELS, ...this.labels };
396
+ const lang = typeof document === "undefined" ? "en" : document.documentElement.lang.toLowerCase();
397
+ return { ...lang.startsWith("es") ? ES_LABELS : DEFAULT_LABELS, ...this.labels };
341
398
  }
342
399
  /** Placeholder efectivo del buscador (prop explícita → label i18n → default inglés). */
343
400
  get effSearchPlaceholder() {
@@ -838,6 +895,8 @@ class OkDataTable extends LitElement {
838
895
  color=${a.color ?? "medium"}
839
896
  ?disabled=${disabled}
840
897
  aria-disabled=${disabled ? "true" : nothing}
898
+ aria-label=${a.label}
899
+ title=${a.label}
841
900
  @click=${() => this.emit("rowAction", { actionId: a.id, row })}
842
901
  >
843
902
  ${loading ? html`<ion-spinner slot="icon-only" name="dots"></ion-spinner>` : a.icon ? html`<ion-icon slot="icon-only" .icon=${okIcon(a.icon)}></ion-icon>` : a.label}
package/dist/ok-navbar.js CHANGED
@@ -22,7 +22,7 @@ function adoptPanelSheet() {
22
22
  const sheet = new CSSStyleSheet();
23
23
  sheet.replaceSync(`
24
24
  ion-modal.ok-navbar-modal {
25
- --width: var(--ok-navbar-panel-width, min(320px, 86vw));
25
+ --width: var(--ok-navbar-panel-width, min(20rem, 86vw));
26
26
  --height: 100%;
27
27
  --border-radius: 0;
28
28
  --background: var(--ok-surface, var(--ion-card-background, #ffffff));
@@ -39,17 +39,26 @@ function adoptPanelSheet() {
39
39
  overflow-y: auto;
40
40
  display: flex;
41
41
  flex-direction: column;
42
- padding: 0.5rem var(--ok-spacing, var(--ion-padding, 16px)) var(--ok-spacing, var(--ion-padding, 16px));
42
+ padding: 0.5rem var(--ok-spacing, var(--ion-padding, 1rem)) max(var(--ok-spacing, var(--ion-padding, 1rem)), env(safe-area-inset-bottom));
43
43
  background: var(--ok-surface, var(--ion-card-background, #ffffff));
44
44
  color: var(--ok-text, var(--ion-text-color, #1c1b17));
45
45
  font-family: var(--ok-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
46
46
  }
47
- .ok-navbar-modal .ok-navbar-panel-head { display: flex; justify-content: flex-end; }
47
+ .ok-navbar-modal .ok-navbar-panel-head {
48
+ display: flex;
49
+ justify-content: flex-end;
50
+ min-height: 2.75rem;
51
+ }
48
52
  .ok-navbar-modal .ok-navbar-close {
49
53
  background: none;
50
54
  border: 0;
51
55
  cursor: pointer;
52
- padding: 0.5rem;
56
+ width: 2.75rem;
57
+ height: 2.75rem;
58
+ padding: 0;
59
+ display: grid;
60
+ place-items: center;
61
+ border-radius: var(--ok-radius-md, var(--ion-border-radius, 0.5rem));
53
62
  color: inherit;
54
63
  }
55
64
  .ok-navbar-modal .ok-navbar-close span { display: block; width: 22px; height: 2px; position: relative; }
@@ -68,11 +77,13 @@ function adoptPanelSheet() {
68
77
  .ok-navbar-modal .ok-navbar-close span::after { transform: rotate(-45deg); }
69
78
  .ok-navbar-modal .ok-navbar-links { display: flex; flex-direction: column; }
70
79
  .ok-navbar-modal .ok-navbar-links a {
71
- display: block;
72
- padding: 0.85rem 0;
80
+ min-height: 3.25rem;
81
+ display: flex;
82
+ align-items: center;
83
+ padding-inline: 0.25rem;
73
84
  color: inherit;
74
85
  text-decoration: none;
75
- font-size: 0.95rem;
86
+ font-size: 1rem;
76
87
  border-top: 1px solid var(--ok-border-soft, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.07));
77
88
  }
78
89
  .ok-navbar-modal .ok-navbar-links a:first-child { border-top: 0; }
@@ -82,11 +93,15 @@ function adoptPanelSheet() {
82
93
  flex-direction: column;
83
94
  align-items: stretch;
84
95
  gap: 0.6rem;
85
- margin-top: 1.25rem;
96
+ margin-top: auto;
86
97
  padding-top: 1.25rem;
87
98
  border-top: 1px solid var(--ok-border, rgba(var(--ion-text-color-rgb, 24, 24, 27), 0.12));
88
99
  }
89
- .ok-navbar-modal .ok-navbar-actions ion-button { width: 100%; }
100
+ .ok-navbar-modal .ok-navbar-actions ion-button {
101
+ width: 100%;
102
+ min-height: 2.75rem;
103
+ margin: 0;
104
+ }
90
105
  `);
91
106
  document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
92
107
  }
@@ -162,11 +177,15 @@ class OkNavbar extends LitElement {
162
177
  }
163
178
 
164
179
  .burger {
165
- display: block;
180
+ width: 2.75rem;
181
+ height: 2.75rem;
182
+ display: grid;
183
+ place-items: center;
166
184
  background: none;
167
185
  border: 0;
168
186
  cursor: pointer;
169
- padding: 0.5rem;
187
+ padding: 0;
188
+ border-radius: var(--ok-radius-md, var(--ion-border-radius, 0.5rem));
170
189
  color: var(--color);
171
190
  /* Micro-interacción: feedback sutil al presionar el burger. */
172
191
  transition: background-color var(--ok-transition, 150ms ease), color var(--ok-transition, 150ms ease), border-color var(--ok-transition, 150ms ease), box-shadow var(--ok-transition, 150ms ease), transform 120ms ease;
@@ -182,6 +201,9 @@ class OkNavbar extends LitElement {
182
201
  .burger span::before, .burger span::after { content: ''; position: absolute; left: 0; width: 22px; height: 2px; background: currentColor; border-radius: 2px; }
183
202
  .burger span::before { top: -7px; }
184
203
  .burger span::after { top: 7px; }
204
+ @media (max-width: 800px) {
205
+ .bar { padding-block: 0.25rem; }
206
+ }
185
207
  `;
186
208
  }
187
209
  get t() {