ui-optago 1.0.1 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-optago",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Design System Óptago — componentes UI em CSS puro + JS vanilla, com tema claro/escuro, verde-lima (#C0FF6B) e app shell com sidebar colapsável.",
5
5
  "author": "Óptago <cowork@optago.com.br> (https://optago.com.br)",
6
6
  "license": "MIT",
package/ui-optago.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /* ============================================================================
2
- UI ÓPTAGO — Design System v1.0.1
2
+ UI ÓPTAGO — Design System v1.0.3
3
3
  CSS puro, sem dependências. Tema escuro por padrão.
4
4
  Para tema claro: adicione a classe "light" ao <html> (o ui-optago.js
5
5
  gerencia isso automaticamente via localStorage).
@@ -74,12 +74,12 @@
74
74
 
75
75
  /* Superfícies — TEMA CLARO */
76
76
  html.light {
77
- --op-bg: #EEF2F7;
78
- --op-panel: #FFFFFF;
79
- --op-panel-2: #F6F8FB;
80
- --op-panel-3: #EDF1F6;
81
- --op-border: #DFE6EE;
82
- --op-border-2: #C6D2DF;
77
+ --op-bg: #E3E9F0;
78
+ --op-panel: #F8FAFC;
79
+ --op-panel-2: #EEF2F7;
80
+ --op-panel-3: #E6EBF2;
81
+ --op-border: #D6DEE7;
82
+ --op-border-2: #BDC9D8;
83
83
  --op-text: #334155;
84
84
  --op-strong: #0F172A;
85
85
  --op-muted: #64748B;
@@ -100,9 +100,9 @@ html.light {
100
100
  /* Fundo do tema claro: gradiente suave com um toque de verde-lima no topo */
101
101
  html.light body {
102
102
  background:
103
- radial-gradient(900px 420px at 75% -10%, rgba(192,255,107,.30) 0%, transparent 60%),
104
- radial-gradient(700px 380px at 10% 110%, rgba(192,255,107,.14) 0%, transparent 60%),
105
- linear-gradient(180deg, #F2F5F9 0%, #E9EEF5 100%);
103
+ radial-gradient(900px 420px at 75% -10%, rgba(192,255,107,.20) 0%, transparent 60%),
104
+ radial-gradient(700px 380px at 10% 110%, rgba(192,255,107,.10) 0%, transparent 60%),
105
+ linear-gradient(180deg, #E7ECF2 0%, #DEE5EC 100%);
106
106
  }
107
107
 
108
108
  /* ----------------------------------------------------------------------------
@@ -140,7 +140,9 @@ h4 { font-size: 1rem; }
140
140
 
141
141
  p { margin: 0 0 1em; }
142
142
  a { color: var(--op-accent); }
143
- html.light a { color: #3F6212; }
143
+ html.light a:not(.op-btn) {
144
+ color: #3F6212;
145
+ }
144
146
  code, kbd { font-family: var(--op-font-mono); font-size: .85em; }
145
147
 
146
148
  kbd {
@@ -279,12 +281,12 @@ html.light .op-brand-mark::after { background: var(--op-accent); }
279
281
  .op-main {
280
282
  flex: 1;
281
283
  overflow-y: auto;
282
- padding: 2rem 1.5rem;
284
+ padding: 1.5rem;
283
285
  scroll-behavior: smooth;
284
286
  position: relative;
285
287
  z-index: 1; /* mantém o conteúdo acima dos .op-glow do fundo */
286
288
  }
287
- @media (min-width: 1024px) { .op-main { padding: 2.5rem; } }
289
+ @media (min-width: 1024px) { .op-main { padding: 1.75rem 2rem; } }
288
290
 
289
291
  .op-container { max-width: 1100px; margin: 0 auto; }
290
292
 
@@ -358,25 +360,28 @@ html.light .op-brand-mark::after { background: var(--op-accent); }
358
360
  align-items: center;
359
361
  gap: .75rem;
360
362
  padding: .625rem .75rem;
363
+ border: 1px solid transparent;
361
364
  border-radius: var(--op-radius);
362
365
  color: var(--op-text);
363
366
  text-decoration: none;
364
367
  font-weight: 600;
365
368
  font-size: .875rem;
366
369
  white-space: nowrap;
367
- transition: background .2s ease, color .2s ease;
370
+ transition: background .2s ease, color .2s ease, border-color .2s ease;
368
371
  }
369
372
  .op-nav-item i { font-size: 1.25rem; flex-shrink: 0; }
370
373
  .op-nav-item:hover {
371
374
  background: color-mix(in srgb, var(--op-strong) 6%, transparent);
375
+ border-color: var(--op-border-2);
372
376
  color: var(--op-strong);
373
377
  }
374
378
  .op-nav-item.is-active {
375
- background: color-mix(in srgb, var(--op-accent) 12%, transparent);
379
+ background: color-mix(in srgb, var(--op-accent) 15%, transparent);
380
+ border-color: color-mix(in srgb, var(--op-accent) 45%, transparent);
376
381
  color: var(--op-strong);
377
382
  box-shadow: inset -3px 0 0 var(--op-accent);
378
383
  }
379
- html.light .op-nav-item.is-active { box-shadow: inset -3px 0 0 #84CC16; }
384
+ html.light .op-nav-item.is-active { box-shadow: inset -3px 0 0 #84CC16; border-color: color-mix(in srgb, #84CC16 45%, transparent); }
380
385
  .op-nav-item.is-active i { color: var(--op-accent); }
381
386
  html.light .op-nav-item.is-active i { color: #3F6212; }
382
387
 
@@ -385,7 +390,7 @@ html.light .op-nav-item.is-active i { color: #3F6212; }
385
390
  .op-sidebar.is-collapsed .op-nav-text,
386
391
  .op-sidebar.is-collapsed .op-sidebar-label,
387
392
  .op-sidebar.is-collapsed .op-sidebar-header-text { display: none; }
388
- .op-sidebar.is-collapsed .op-nav-item { justify-content: center; padding-left: 0; padding-right: 0; }
393
+ .op-sidebar.is-collapsed .op-nav-item { justify-content: center; padding: .625rem; }
389
394
 
390
395
  /* Tooltip do item (aparece à esquerda quando colapsada).
391
396
  position:fixed — o JS calcula top/right no hover, pois o overflow-y:auto
@@ -710,6 +715,19 @@ html.light .op-input:focus, html.light .op-select:focus, html.light .op-textarea
710
715
  }
711
716
  .op-input-icon .op-input { padding-left: 2.625rem; }
712
717
 
718
+ /* Botão de mostrar/ocultar senha: envolva o input com [data-op-reveal] e
719
+ adicione um <button data-op-reveal-toggle> dentro — o toggle de type
720
+ password/text é feito automaticamente pelo JS da lib. */
721
+ [data-op-reveal] { position: relative; }
722
+ [data-op-reveal] .op-input { padding-right: 2.5rem; }
723
+ .op-reveal-btn {
724
+ position: absolute; top: 50%; right: .5rem; translate: 0 -50%;
725
+ background: none; border: 0; cursor: pointer; padding: .25rem;
726
+ color: var(--op-muted); font-size: 1.125rem; line-height: 1;
727
+ display: flex; align-items: center;
728
+ }
729
+ .op-reveal-btn:hover { color: var(--op-text); }
730
+
713
731
  /* Input group (prefixo/sufixo) */
714
732
  .op-input-group { display: flex; }
715
733
  .op-input-group .op-input { border-radius: 0; }
@@ -1415,6 +1433,11 @@ html.light .op-modal { background: rgba(15,23,42,.5); }
1415
1433
  border-top: 1px solid var(--op-border);
1416
1434
  background: var(--op-panel-2);
1417
1435
  }
1436
+ /* Modais com formulário real (method="post") costumam envolver o body e o
1437
+ footer num único <form> para o submit incluir os dois. Sem isso, o <form>
1438
+ (um bloco comum) quebra a coluna flex do .op-modal-box e o body deixa de
1439
+ rolar internamente — o modal cresce para fora da tela em vez de rolar. */
1440
+ .op-modal form { display: contents; }
1418
1441
 
1419
1442
  /* ----------------------------------------------------------------------------
1420
1443
  19. TOASTS (criados via OptagoUI.toast)
@@ -1489,17 +1512,53 @@ html.light .op-modal { background: rgba(15,23,42,.5); }
1489
1512
  .op-toast-danger .op-toast-icon { background: color-mix(in srgb, var(--op-danger) 15%, transparent); color: var(--op-danger); border-color: color-mix(in srgb, var(--op-danger) 30%, transparent); }
1490
1513
 
1491
1514
  /* ----------------------------------------------------------------------------
1492
- 20. BLOCO DE DEMONSTRAÇÃO (showcase: preview + copiar + ver código)
1515
+ 20. TELA DE CARREGAMENTO (overlay em tela cheia)
1516
+ Uso: OptagoUI.showLoading('Mensagem...') / OptagoUI.hideLoading()
1517
+ ou data-op-loading-form no <form> para acionar sozinho no submit.
1518
+ ---------------------------------------------------------------------------- */
1519
+ .op-loading-overlay {
1520
+ position: fixed;
1521
+ inset: 0;
1522
+ z-index: 150;
1523
+ display: none;
1524
+ flex-direction: column;
1525
+ align-items: center;
1526
+ justify-content: center;
1527
+ gap: 1.25rem;
1528
+ background: color-mix(in srgb, var(--op-bg) 85%, transparent);
1529
+ backdrop-filter: blur(8px);
1530
+ -webkit-backdrop-filter: blur(8px);
1531
+ opacity: 0;
1532
+ transition: opacity .25s var(--op-ease);
1533
+ }
1534
+ .op-loading-overlay.is-open { display: flex; }
1535
+ .op-loading-overlay.is-visible { opacity: 1; }
1536
+ .op-loading-overlay .op-spinner { width: 46px; height: 46px; border-width: 3px; }
1537
+ .op-loading-message {
1538
+ max-width: 320px;
1539
+ padding: 0 1.5rem;
1540
+ font-family: var(--op-font-mono);
1541
+ font-size: .875rem;
1542
+ font-weight: 600;
1543
+ letter-spacing: .01em;
1544
+ text-align: center;
1545
+ color: var(--op-strong);
1546
+ }
1547
+
1548
+ /* ----------------------------------------------------------------------------
1549
+ 21. BLOCO DE DEMONSTRAÇÃO (showcase: preview + copiar + ver código)
1493
1550
  ---------------------------------------------------------------------------- */
1494
1551
  .op-demo {
1495
1552
  background: var(--op-panel);
1496
1553
  border: 1px solid var(--op-border);
1497
1554
  border-radius: var(--op-radius-lg);
1498
- overflow: hidden;
1499
1555
  box-shadow: var(--op-shadow);
1500
1556
  }
1557
+ /* Sem overflow:hidden no container: dropdowns/tooltips exibidos dentro do
1558
+ preview não podem ser cortados. O arredondamento é feito nos filhos. */
1501
1559
  .op-demo-preview {
1502
- padding: 2rem;
1560
+ padding: 1.5rem;
1561
+ border-radius: var(--op-radius-lg) var(--op-radius-lg) 0 0;
1503
1562
  background:
1504
1563
  radial-gradient(color-mix(in srgb, var(--op-border) 55%, transparent) 1px, transparent 1px);
1505
1564
  background-size: 22px 22px;
@@ -1511,7 +1570,11 @@ html.light .op-modal { background: rgba(15,23,42,.5); }
1511
1570
  padding: .625rem .875rem;
1512
1571
  border-top: 1px solid var(--op-border);
1513
1572
  background: var(--op-panel-2);
1573
+ border-radius: 0 0 var(--op-radius-lg) var(--op-radius-lg);
1514
1574
  }
1575
+ /* Quando o código-fonte está visível, ele passa a ser o último bloco:
1576
+ a barra perde o arredondamento inferior e o código assume. */
1577
+ .op-demo-bar:has(+ .op-demo-code.is-visible) { border-radius: 0; }
1515
1578
  .op-demo-code {
1516
1579
  display: none;
1517
1580
  margin: 0;
@@ -1525,6 +1588,7 @@ html.light .op-modal { background: rgba(15,23,42,.5); }
1525
1588
  overflow-x: auto;
1526
1589
  tab-size: 2;
1527
1590
  white-space: pre;
1591
+ border-radius: 0 0 var(--op-radius-lg) var(--op-radius-lg);
1528
1592
  }
1529
1593
  .op-demo-code.is-visible { display: block; }
1530
1594
 
@@ -1533,14 +1597,14 @@ html.light .op-modal { background: rgba(15,23,42,.5); }
1533
1597
  display: flex;
1534
1598
  align-items: center;
1535
1599
  gap: .75rem;
1536
- padding-bottom: 1rem;
1537
- margin-bottom: 1.5rem;
1600
+ padding-bottom: .75rem;
1601
+ margin-bottom: 1.125rem;
1538
1602
  border-bottom: 1px solid var(--op-border);
1539
1603
  }
1540
1604
  .op-section-head i {
1541
- font-size: 1.375rem;
1605
+ font-size: 1.25rem;
1542
1606
  color: var(--op-accent);
1543
- width: 44px; height: 44px;
1607
+ width: 38px; height: 38px;
1544
1608
  display: flex;
1545
1609
  align-items: center;
1546
1610
  justify-content: center;
@@ -1867,7 +1931,7 @@ html.light .op-tabulator.tabulator .tabulator-footer .tabulator-page.active {
1867
1931
  .op-tabulator .op-progress { min-width: 90px; }
1868
1932
 
1869
1933
  /* ----------------------------------------------------------------------------
1870
- 21. UTILITÁRIOS
1934
+ 22. UTILITÁRIOS
1871
1935
  ---------------------------------------------------------------------------- */
1872
1936
  .op-row { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
1873
1937
  .op-stack { display: flex; flex-direction: column; gap: 1rem; }
@@ -1886,11 +1950,11 @@ html.light .op-text-accent { color: #3F6212; }
1886
1950
  .op-text-sm { font-size: .8125rem; }
1887
1951
  .op-text-xs { font-size: .6875rem; }
1888
1952
 
1889
- .op-mt-1 { margin-top: .5rem; } .op-mt-2 { margin-top: 1rem; } .op-mt-3 { margin-top: 1.5rem; } .op-mt-4 { margin-top: 2.5rem; }
1890
- .op-mb-1 { margin-bottom: .5rem; } .op-mb-2 { margin-bottom: 1rem; } .op-mb-3 { margin-bottom: 1.5rem; } .op-mb-4 { margin-bottom: 2.5rem; }
1953
+ .op-mt-1 { margin-top: .5rem; } .op-mt-2 { margin-top: 1rem; } .op-mt-3 { margin-top: 1.5rem; } .op-mt-4 { margin-top: 1.75rem; }
1954
+ .op-mb-1 { margin-bottom: .5rem; } .op-mb-2 { margin-bottom: 1rem; } .op-mb-3 { margin-bottom: 1.5rem; } .op-mb-4 { margin-bottom: 1.75rem; }
1891
1955
 
1892
1956
  /* ----------------------------------------------------------------------------
1893
- 22. ANIMAÇÕES
1957
+ 23. ANIMAÇÕES
1894
1958
  ---------------------------------------------------------------------------- */
1895
1959
  @keyframes op-spin { to { transform: rotate(360deg); } }
1896
1960
  @keyframes op-bounce {
package/ui-optago.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* ============================================================================
2
- UI ÓPTAGO — Design System v1.0.1 (JavaScript)
2
+ UI ÓPTAGO — Design System v1.0.3 (JavaScript)
3
3
  Sem dependências de build. Carregue no <head> SEM defer para evitar
4
4
  flash de tema errado (FOUC):
5
5
 
@@ -21,9 +21,13 @@
21
21
  data-op-dismiss → remove o elemento pai .op-alert
22
22
  data-op-copy="#seletor" → copia o HTML do elemento alvo
23
23
  data-op-code="#seletor" → mostra/esconde o código-fonte do alvo
24
+ data-op-loading-form → no <form>: mostra a tela de carregamento
25
+ sozinho ao submeter (mensagem opcional em
26
+ data-op-loading-message="Enviando...")
24
27
 
25
28
  API global: OptagoUI.toast(msg, tipo, ms) · OptagoUI.openModal(sel)
26
29
  OptagoUI.closeModal(sel) · OptagoUI.setTheme('dark'|'light')
30
+ OptagoUI.showLoading(msg) · OptagoUI.hideLoading()
27
31
  ============================================================================ */
28
32
  (function () {
29
33
  'use strict';
@@ -241,6 +245,53 @@
241
245
  });
242
246
  };
243
247
 
248
+ /* --------------------------------------------------------------------------
249
+ 5c. TELA DE CARREGAMENTO — overlay em tela cheia
250
+ Uso: OptagoUI.showLoading('Salvando...') ... OptagoUI.hideLoading()
251
+ ou data-op-loading-form no <form> para acionar sozinho no submit.
252
+ -------------------------------------------------------------------------- */
253
+ var LOADING_ID = 'opLoadingOverlay';
254
+
255
+ function getLoadingOverlay() {
256
+ var ov = document.getElementById(LOADING_ID);
257
+ if (!ov) {
258
+ ov = document.createElement('div');
259
+ ov.id = LOADING_ID;
260
+ ov.className = 'op-loading-overlay';
261
+ ov.setAttribute('role', 'alert');
262
+ ov.setAttribute('aria-live', 'assertive');
263
+ ov.innerHTML =
264
+ '<span class="op-spinner"></span>' +
265
+ '<span class="op-loading-message"></span>';
266
+ document.body.appendChild(ov);
267
+ }
268
+ return ov;
269
+ }
270
+
271
+ OptagoUI.showLoading = function (message) {
272
+ var ov = getLoadingOverlay();
273
+ ov.querySelector('.op-loading-message').textContent = message || 'Carregando...';
274
+ ov.classList.add('is-open');
275
+ requestAnimationFrame(function () {
276
+ requestAnimationFrame(function () { ov.classList.add('is-visible'); });
277
+ });
278
+ };
279
+
280
+ OptagoUI.hideLoading = function () {
281
+ var ov = document.getElementById(LOADING_ID);
282
+ if (!ov) return;
283
+ ov.classList.remove('is-visible');
284
+ setTimeout(function () { ov.classList.remove('is-open'); }, 250);
285
+ };
286
+
287
+ // Ao voltar pelo botão "Voltar" do navegador, a página pode ser restaurada
288
+ // do bfcache exatamente como estava ao sair (overlay de carregamento ainda
289
+ // visível, já que o clique/submit anterior nunca chegou a escondê-lo).
290
+ // pageshow com event.persisted=true é o sinal de que veio do bfcache.
291
+ window.addEventListener('pageshow', function (e) {
292
+ if (e.persisted) OptagoUI.hideLoading();
293
+ });
294
+
244
295
  /* --------------------------------------------------------------------------
245
296
  6. HELPERS DO SHOWCASE — copiar HTML / ver código
246
297
  -------------------------------------------------------------------------- */
@@ -379,6 +430,22 @@
379
430
  // Ver código
380
431
  t = e.target.closest('[data-op-code]');
381
432
  if (t) { OptagoUI.toggleCode(t.getAttribute('data-op-code'), t); return; }
433
+
434
+ // Mostrar/ocultar senha: [data-op-reveal] envolvendo o input,
435
+ // com um <button data-op-reveal-toggle> dentro.
436
+ t = e.target.closest('[data-op-reveal-toggle]');
437
+ if (t) {
438
+ var wrap = t.closest('[data-op-reveal]');
439
+ var input = wrap ? wrap.querySelector('input') : null;
440
+ if (input) {
441
+ var show = input.type === 'password';
442
+ input.type = show ? 'text' : 'password';
443
+ var icon = t.querySelector('i');
444
+ if (icon) icon.className = show ? 'ph ph-eye-slash' : 'ph ph-eye';
445
+ t.setAttribute('aria-label', show ? 'Ocultar senha' : 'Mostrar senha');
446
+ }
447
+ return;
448
+ }
382
449
  });
383
450
 
384
451
  // Tooltip da sidebar colapsada: o overflow-y da nav cortaria um tooltip
@@ -395,6 +462,14 @@
395
462
  tip.style.left = 'auto';
396
463
  });
397
464
 
465
+ // Formulário com data-op-loading-form: mostra a tela de carregamento
466
+ // sozinho ao submeter (mensagem em data-op-loading-message, se houver).
467
+ document.addEventListener('submit', function (e) {
468
+ var form = e.target.closest('[data-op-loading-form]');
469
+ if (!form) return;
470
+ OptagoUI.showLoading(form.getAttribute('data-op-loading-message'));
471
+ });
472
+
398
473
  // ESC fecha modais e dropdowns
399
474
  document.addEventListener('keydown', function (e) {
400
475
  if (e.key !== 'Escape') return;