anteriority-ui 1.4.0 → 1.5.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.
@@ -1187,9 +1187,25 @@
1187
1187
  background-color: #ffffff;
1188
1188
  z-index: 10;
1189
1189
  }
1190
+ .Frame_skeletonGroup {
1191
+ width: 100%;
1192
+ display: flex;
1193
+ flex-direction: column;
1194
+ gap: 0.6rem;
1195
+ }
1190
1196
  .Frame_hiddenContent {
1191
1197
  visibility: hidden;
1192
1198
  }
1199
+ .Frame_contentWrapper {
1200
+ display: flex;
1201
+ flex-direction: column;
1202
+ min-height: 0;
1203
+ width: 100%;
1204
+ }
1205
+ .Frame_scrollContent {
1206
+ display: flex;
1207
+ flex-direction: column;
1208
+ }
1193
1209
 
1194
1210
  /* dist/screens/components/Loading/index.module.css */
1195
1211
  .Loading_loadingContainer {
@@ -1393,6 +1409,108 @@
1393
1409
  transform: translateY(-4px);
1394
1410
  }
1395
1411
 
1412
+ /* dist/screens/components/Skeleton/index.module.css */
1413
+ .Skeleton_skeleton {
1414
+ position: relative;
1415
+ display: inline-block;
1416
+ overflow: hidden;
1417
+ background: rgba(128, 128, 128, 0.18);
1418
+ }
1419
+ .Skeleton_skeleton::after {
1420
+ content: "";
1421
+ position: absolute;
1422
+ inset: 0;
1423
+ transform: translateX(-100%);
1424
+ background:
1425
+ linear-gradient(
1426
+ 90deg,
1427
+ rgba(255, 255, 255, 0) 0%,
1428
+ rgba(255, 255, 255, 0.35) 50%,
1429
+ rgba(255, 255, 255, 0) 100%);
1430
+ animation: Skeleton_skeletonSweep 1.6s ease-in-out infinite;
1431
+ }
1432
+ @keyframes Skeleton_skeletonSweep {
1433
+ 100% {
1434
+ transform: translateX(100%);
1435
+ }
1436
+ }
1437
+
1438
+ /* dist/screens/components/ScrollBar/index.module.css */
1439
+ .ScrollBar_root {
1440
+ position: relative;
1441
+ width: 100%;
1442
+ }
1443
+ .ScrollBar_scrollArea {
1444
+ width: 100%;
1445
+ height: 100%;
1446
+ box-sizing: border-box;
1447
+ scrollbar-width: none;
1448
+ -ms-overflow-style: none;
1449
+ }
1450
+ .ScrollBar_reserveTrackSpace {
1451
+ padding-right: 14px;
1452
+ }
1453
+ .ScrollBar_scrollArea::-webkit-scrollbar {
1454
+ display: none;
1455
+ }
1456
+ .ScrollBar_trackVertical {
1457
+ position: absolute;
1458
+ top: 4px;
1459
+ right: 4px;
1460
+ bottom: 4px;
1461
+ width: 6px;
1462
+ border-radius: 999px;
1463
+ background-color: rgba(148, 163, 184, 0.12);
1464
+ opacity: 0;
1465
+ pointer-events: none;
1466
+ transition: opacity 0.2s ease;
1467
+ z-index: 5;
1468
+ }
1469
+ .ScrollBar_trackHorizontal {
1470
+ position: absolute;
1471
+ left: 4px;
1472
+ right: 4px;
1473
+ bottom: 3px;
1474
+ height: 8px;
1475
+ border-radius: 999px;
1476
+ background-color: rgba(148, 163, 184, 0.12);
1477
+ opacity: 0;
1478
+ pointer-events: none;
1479
+ transition: opacity 0.2s ease;
1480
+ z-index: 5;
1481
+ }
1482
+ .ScrollBar_trackVisible {
1483
+ opacity: 1;
1484
+ }
1485
+ .ScrollBar_thumbVertical {
1486
+ position: absolute;
1487
+ top: 0;
1488
+ right: 0;
1489
+ width: 100%;
1490
+ border-radius: 999px;
1491
+ background-color: rgba(100, 116, 139, 0.45);
1492
+ cursor: pointer;
1493
+ pointer-events: auto;
1494
+ transition: background-color 0.15s ease;
1495
+ }
1496
+ .ScrollBar_thumbHorizontal {
1497
+ position: absolute;
1498
+ left: 0;
1499
+ top: 0;
1500
+ height: 100%;
1501
+ border-radius: 999px;
1502
+ background-color: rgba(100, 116, 139, 0.45);
1503
+ cursor: pointer;
1504
+ pointer-events: auto;
1505
+ transition: background-color 0.15s ease;
1506
+ }
1507
+ .ScrollBar_thumbVertical:hover,
1508
+ .ScrollBar_thumbVertical:active,
1509
+ .ScrollBar_thumbHorizontal:hover,
1510
+ .ScrollBar_thumbHorizontal:active {
1511
+ background-color: rgba(71, 85, 105, 0.75);
1512
+ }
1513
+
1396
1514
  /* dist/screens/components/MarkdownContent/index.module.css */
1397
1515
  .MarkdownContent_markdown {
1398
1516
  font-size: 0.88rem;
@@ -1598,6 +1716,18 @@
1598
1716
  border-radius: 8px;
1599
1717
  box-sizing: border-box;
1600
1718
  animation: Callout_calloutSlideIn 0.2s ease-out;
1719
+ text-decoration: none;
1720
+ }
1721
+ .Callout_clickable {
1722
+ cursor: pointer;
1723
+ transition: filter 0.15s ease, box-shadow 0.15s ease;
1724
+ }
1725
+ .Callout_clickable:hover {
1726
+ filter: brightness(0.97);
1727
+ }
1728
+ .Callout_clickable:focus-visible {
1729
+ outline: 2px solid currentColor;
1730
+ outline-offset: 2px;
1601
1731
  }
1602
1732
  .Callout_icon {
1603
1733
  display: inline-flex;
@@ -1617,6 +1747,11 @@
1617
1747
  .Callout_markdownBody {
1618
1748
  font-size: 0.85rem;
1619
1749
  }
1750
+ .Callout_timestamp {
1751
+ font-size: 0.72rem;
1752
+ opacity: 0.7;
1753
+ margin-top: 4px;
1754
+ }
1620
1755
  .Callout_actions {
1621
1756
  display: flex;
1622
1757
  flex-wrap: wrap;
@@ -3292,6 +3427,13 @@
3292
3427
  white-space: normal !important;
3293
3428
  overflow-wrap: anywhere !important;
3294
3429
  }
3430
+ .IconCatalog_packBadge {
3431
+ font-size: 0.65rem;
3432
+ color: var(--color-text-muted, #64748b);
3433
+ background: var(--color-border, #e2e8f0);
3434
+ border-radius: 999px;
3435
+ padding: 0.1rem 0.5rem;
3436
+ }
3295
3437
  .IconCatalog_empty {
3296
3438
  font-size: 0.9rem;
3297
3439
  color: var(--color-text-muted, #64748b);
@@ -3612,6 +3754,33 @@
3612
3754
  margin-top: 2px;
3613
3755
  }
3614
3756
 
3757
+ /* dist/screens/components/NotificationsPanel/index.module.css */
3758
+ .NotificationsPanel_panel {
3759
+ width: 360px;
3760
+ padding: 1.5rem;
3761
+ }
3762
+ .NotificationsPanel_list {
3763
+ display: flex;
3764
+ flex-direction: column;
3765
+ gap: 0.75rem;
3766
+ min-height: 0;
3767
+ }
3768
+ .NotificationsPanel_emptyMessage {
3769
+ margin: 1rem 0;
3770
+ text-align: center;
3771
+ font-size: 0.85rem;
3772
+ color: #94a3b8;
3773
+ }
3774
+ .NotificationsPanel_item {
3775
+ align-items: flex-start;
3776
+ }
3777
+ .NotificationsPanel_itemActions {
3778
+ display: flex;
3779
+ flex-direction: column;
3780
+ gap: 0.5rem;
3781
+ margin-top: 0.25rem;
3782
+ }
3783
+
3615
3784
  /* dist/screens/components/ToggleCard/index.module.css */
3616
3785
  .ToggleCard_toggleContainer {
3617
3786
  background: #ffffff;
@@ -3859,104 +4028,6 @@
3859
4028
  }
3860
4029
  }
3861
4030
 
3862
- /* dist/screens/components/ScrollBar/index.module.css */
3863
- .ScrollBar_root {
3864
- position: relative;
3865
- width: 100%;
3866
- }
3867
- .ScrollBar_scrollArea {
3868
- width: 100%;
3869
- height: 100%;
3870
- scrollbar-width: none;
3871
- -ms-overflow-style: none;
3872
- }
3873
- .ScrollBar_scrollArea::-webkit-scrollbar {
3874
- display: none;
3875
- }
3876
- .ScrollBar_trackVertical {
3877
- position: absolute;
3878
- top: 4px;
3879
- right: 3px;
3880
- bottom: 4px;
3881
- width: 8px;
3882
- border-radius: 999px;
3883
- background-color: rgba(148, 163, 184, 0.12);
3884
- opacity: 0;
3885
- pointer-events: none;
3886
- transition: opacity 0.2s ease;
3887
- z-index: 5;
3888
- }
3889
- .ScrollBar_trackHorizontal {
3890
- position: absolute;
3891
- left: 4px;
3892
- right: 4px;
3893
- bottom: 3px;
3894
- height: 8px;
3895
- border-radius: 999px;
3896
- background-color: rgba(148, 163, 184, 0.12);
3897
- opacity: 0;
3898
- pointer-events: none;
3899
- transition: opacity 0.2s ease;
3900
- z-index: 5;
3901
- }
3902
- .ScrollBar_trackVisible {
3903
- opacity: 1;
3904
- }
3905
- .ScrollBar_thumbVertical {
3906
- position: absolute;
3907
- top: 0;
3908
- right: 0;
3909
- width: 100%;
3910
- border-radius: 999px;
3911
- background-color: rgba(100, 116, 139, 0.45);
3912
- cursor: pointer;
3913
- pointer-events: auto;
3914
- transition: background-color 0.15s ease;
3915
- }
3916
- .ScrollBar_thumbHorizontal {
3917
- position: absolute;
3918
- left: 0;
3919
- top: 0;
3920
- height: 100%;
3921
- border-radius: 999px;
3922
- background-color: rgba(100, 116, 139, 0.45);
3923
- cursor: pointer;
3924
- pointer-events: auto;
3925
- transition: background-color 0.15s ease;
3926
- }
3927
- .ScrollBar_thumbVertical:hover,
3928
- .ScrollBar_thumbVertical:active,
3929
- .ScrollBar_thumbHorizontal:hover,
3930
- .ScrollBar_thumbHorizontal:active {
3931
- background-color: rgba(71, 85, 105, 0.75);
3932
- }
3933
-
3934
- /* dist/screens/components/Skeleton/index.module.css */
3935
- .Skeleton_skeleton {
3936
- position: relative;
3937
- display: inline-block;
3938
- overflow: hidden;
3939
- background: rgba(128, 128, 128, 0.18);
3940
- }
3941
- .Skeleton_skeleton::after {
3942
- content: "";
3943
- position: absolute;
3944
- inset: 0;
3945
- transform: translateX(-100%);
3946
- background:
3947
- linear-gradient(
3948
- 90deg,
3949
- rgba(255, 255, 255, 0) 0%,
3950
- rgba(255, 255, 255, 0.35) 50%,
3951
- rgba(255, 255, 255, 0) 100%);
3952
- animation: Skeleton_skeletonSweep 1.6s ease-in-out infinite;
3953
- }
3954
- @keyframes Skeleton_skeletonSweep {
3955
- 100% {
3956
- transform: translateX(100%);
3957
- }
3958
- }
3959
-
3960
4031
  /* dist/screens/components/Sidebar/index.module.css */
3961
4032
  .Sidebar_sidebar {
3962
4033
  height: 100vh;
@@ -4178,6 +4249,67 @@
4178
4249
  .Sidebar_sidebarItemBtn:hover .Sidebar_icon {
4179
4250
  color: var(--sidebar-text-active) !important;
4180
4251
  }
4252
+ .Sidebar_itemWithSubItems {
4253
+ display: flex;
4254
+ flex-direction: column;
4255
+ }
4256
+ .Sidebar_expandChevron {
4257
+ flex-shrink: 0;
4258
+ color: var(--sidebar-text-muted);
4259
+ opacity: 0.7;
4260
+ transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
4261
+ }
4262
+ .Sidebar_expandChevronOpen {
4263
+ transform: rotate(180deg);
4264
+ opacity: 1;
4265
+ }
4266
+ .Sidebar_expandParentOpen {
4267
+ color: var(--sidebar-text-active) !important;
4268
+ font-weight: 600 !important;
4269
+ }
4270
+ .Sidebar_expandParentOpen .Sidebar_icon {
4271
+ color: var(--sidebar-text-active) !important;
4272
+ }
4273
+ .Sidebar_subItemListWrapper {
4274
+ display: grid;
4275
+ grid-template-rows: 0fr;
4276
+ margin-top: 0;
4277
+ transition: grid-template-rows 0.28s cubic-bezier(0.4, 0, 0.2, 1), margin-top 0.28s cubic-bezier(0.4, 0, 0.2, 1);
4278
+ }
4279
+ .Sidebar_subItemListWrapperOpen {
4280
+ grid-template-rows: 1fr;
4281
+ margin-top: 2px;
4282
+ }
4283
+ .Sidebar_subItemList {
4284
+ overflow: hidden;
4285
+ display: flex;
4286
+ flex-direction: column;
4287
+ gap: 2px;
4288
+ margin-left: 29px;
4289
+ padding: 2px 0 2px 14px;
4290
+ border-left: 1.5px solid var(--sidebar-border);
4291
+ opacity: 0;
4292
+ transform: translateY(-4px);
4293
+ transition: opacity 0.16s ease, transform 0.16s ease;
4294
+ }
4295
+ .Sidebar_subItemListWrapperOpen .Sidebar_subItemList {
4296
+ opacity: 1;
4297
+ transform: translateY(0);
4298
+ transition: opacity 0.22s ease 0.08s, transform 0.22s ease 0.08s;
4299
+ }
4300
+ .Sidebar_subItemBtn {
4301
+ padding: 7px 10px !important;
4302
+ min-height: 0 !important;
4303
+ }
4304
+ .Sidebar_subItemBtn .Sidebar_label {
4305
+ font-size: 0.82rem;
4306
+ font-weight: 500;
4307
+ }
4308
+ .Sidebar_subItemBtn.Sidebar_activeBtn {
4309
+ background-color: var(--sidebar-subitem-active-bg, #e0edff) !important;
4310
+ color: var(--sidebar-subitem-active-text, #1d4ed8) !important;
4311
+ font-weight: 600 !important;
4312
+ }
4181
4313
  .Sidebar_activeBtn {
4182
4314
  background-color: var(--sidebar-hover-bg) !important;
4183
4315
  color: var(--sidebar-text-active) !important;
package/.verify-bundle.js CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/.verify-bundle.js
3
- size: 53377441 bytes
4
- sha256: 41494ab04c027fb299fd3a89b3a7d4e147a0ba85593a36164011b5221a81f68f
3
+ size: 53387707 bytes
4
+ sha256: 95636eacde0da07e512202f5b6d0bb1bf9bd83b31d3484db025dc95b3935b7cf
package/AnteriorityUI.md CHANGED
@@ -440,7 +440,7 @@ En modo `range`, la selección soporta dos gestos, intercambiables: (1) click en
440
440
 
441
441
  ### Callout
442
442
 
443
- Caja de aviso/notificación (advertencia, error, éxito, info, nota neutral) con ícono y paleta de color por `variant`, título opcional y cuerpo en Markdown completo (listas, negrita, links, bloques de código, tablas, etc. — reutiliza `MarkdownContent` por dentro). Pensado para usarse suelto en una vista, dentro de `DrawPanel`/`Frame`, o como notificación.
443
+ Caja de aviso/notificación (advertencia, error, éxito, info, nota neutral) con ícono y paleta de color por `variant`, título opcional, timestamp opcional y cuerpo en Markdown completo (listas, negrita, links, bloques de código, tablas, etc. — reutiliza `MarkdownContent` por dentro). Pensado para usarse suelto en una vista, dentro de `DrawPanel`/`Frame`, o como notificación (es el building block que usa `NotificationsPanel` para cada item de la lista). Opcionalmente clickeable/navegable vía `onClick`/`href`.
444
444
 
445
445
  **Import:**
446
446
  ```jsx
@@ -461,10 +461,13 @@ import Callout from '../Callout/index'; // export default
461
461
  | `variant` | `'info'` \| `'warning'` \| `'danger'` \| `'success'` \| `'neutral'` | `'info'` | Determina paleta (fondo/borde/texto/ícono) e ícono por default. |
462
462
  | `title` | string | — | Título en negrita sobre el cuerpo. |
463
463
  | `children` | string | — | Cuerpo en Markdown (mismo formato que `MarkdownContent`). |
464
+ | `timestamp` | string | — | Texto pequeño (ej. `"Hace 5 minutos"`) mostrado debajo del cuerpo. |
464
465
  | `icon` | component | — | Reemplaza el ícono default del `variant`. |
465
- | `actions` | node | — | Contenido (ej. botones) renderizado debajo del cuerpo. |
466
- | `onClose` | `function()` | — | Si se define, muestra un botón de cerrar (×) arriba a la derecha. |
466
+ | `actions` | node | — | Contenido (ej. botones) renderizado debajo del cuerpo. Los clicks dentro de `actions` no disparan `onClick`/`href` del Callout (`stopPropagation`). |
467
+ | `onClose` | `function()` | — | Si se define, muestra un botón de cerrar (×) arriba a la derecha. Su click tampoco dispara `onClick`/`href`. |
467
468
  | `closeLabel` | string | `'Cerrar aviso'` | `aria-label` del botón de cerrar. |
469
+ | `onClick` | `function(event)` | — | Si se define (y no hay `href`), el Callout entero se vuelve clickeable: se renderiza como `<div role="button" tabIndex={0}>` con soporte de teclado (Enter/Espacio) y cursor pointer. |
470
+ | `href` | string | — | Si se define, el Callout se renderiza como `<a href={href}>` (navegación nativa: Ctrl+click, abrir en pestaña nueva, etc.). Tiene prioridad sobre `onClick` como tag, pero `onClick` igual se dispara si también se pasa. |
468
471
  | `bgColor` | string | — | Sobrescribe el color de fondo de la paleta del `variant`. |
469
472
  | `textColor` | string | — | Sobrescribe el color del título y del cuerpo (markdown incluido). |
470
473
  | `iconColor` | string | — | Sobrescribe el color del ícono. |
@@ -1123,7 +1126,7 @@ import { FormCard, FormInput, FormTextArea } from '../FormControls/index'; // na
1123
1126
 
1124
1127
  ### Frame
1125
1128
 
1126
- Contenedor tipo "card" con título, ícono, descripción y estado de error/carga opcionales. Puede renderizarse inline o como modal (overlay + portal).
1129
+ Contenedor tipo "card" con título, ícono, descripción y estado de error/carga opcionales. Puede renderizarse inline o como modal (overlay + portal). Opcionalmente scrollea su contenido internamente (vía `ScrollBar`, sin barra de scroll nativa) cuando el contenido supera un alto máximo.
1127
1130
 
1128
1131
  **Import:**
1129
1132
  ```jsx
@@ -1147,7 +1150,12 @@ import Frame from '../Frame/index'; // export default, archivo index.jsx
1147
1150
  | className | string | `''` | Clase(s) CSS adicionales. |
1148
1151
  | isModal | boolean | `false` | Si es `true`, renderiza el frame como overlay modal vía `createPortal` a `document.body`. |
1149
1152
  | onClose | function | — | Se ejecuta al hacer click en el overlay (solo aplica con `isModal`). |
1150
- | isLoading | boolean | `false` | Muestra un overlay con `Loading` (`size="giant"`) y oculta el contenido. |
1153
+ | isLoading | boolean | `false` | Muestra un overlay sobre el contenido (oculto con `visibility: hidden`, no desmontado) mientras carga. El tipo de overlay lo decide `loadingType`. |
1154
+ | loadingType | `'spinner'` \| `'skeleton'` | `'spinner'` | Con `isLoading`: `'spinner'` muestra `Loading` (`size="giant"`); `'skeleton'` muestra `skeletonCount` barras `Skeleton` genéricas apiladas. |
1155
+ | loadingVariant | string | `'default'` | Con `loadingType="spinner"`, se pasa tal cual como prop `variant` a `Loading` (`'default'` \| `'bars'` \| `'bounce'` \| `'box'`). |
1156
+ | skeletonCount | number | `3` | Con `loadingType="skeleton"`, cantidad de barras `Skeleton` a mostrar. |
1157
+ | scrollable | boolean | `false` | Si es `true`, envuelve `children` con `ScrollBar` (scroll interno sin barra nativa, thumb delgado visible solo on hover/scroll). |
1158
+ | maxHeight | number \| string | — | Con `scrollable`, alto máximo del área scrolleable (pasa a `ScrollBar` como `maxHeight`). Sin `maxHeight`, el contenido crece libremente (el `ScrollBar` no limita el alto por sí solo). |
1151
1159
  | bgColor | string | — | Color de fondo del frame (`--frame-bg`). |
1152
1160
  | iconColor | string | — | Color del contenedor del ícono (`--frame-icon`). |
1153
1161
  | descriptionBgColor | string | — | Color de fondo del texto de descripción (`--frame-description-bg`). |
@@ -1161,8 +1169,20 @@ import Frame from '../Frame/index'; // export default, archivo index.jsx
1161
1169
  <p>Este contenido queda oculto mientras carga.</p>
1162
1170
  </Frame>
1163
1171
  ```
1172
+ ```jsx
1173
+ // Overlay tipo skeleton en vez del spinner default
1174
+ <Frame title="Cargando datos" isLoading loadingType="skeleton" skeletonCount={4}>
1175
+ <p>Este contenido queda oculto mientras carga.</p>
1176
+ </Frame>
1177
+ ```
1178
+ ```jsx
1179
+ // Lista larga con scroll interno (sin barra de scroll nativa)
1180
+ <Frame title="Lista larga" scrollable maxHeight={220}>
1181
+ {items.map((item) => <p key={item.id}>{item.label}</p>)}
1182
+ </Frame>
1183
+ ```
1164
1184
 
1165
- **Notas:** `isModal` renderiza vía `createPortal` sobre `document.body` con un overlay que cierra al hacer click fuera (así lo usa internamente `HoursPicker`). Depende del componente `Loading` para el estado `isLoading`.
1185
+ **Notas:** `isModal` renderiza vía `createPortal` sobre `document.body` con un overlay que cierra al hacer click fuera (así lo usa internamente `HoursPicker`). El estado `isLoading` depende de `Loading` (modo `'spinner'`, default) o de `Skeleton` (modo `'skeleton'`) — el skeleton es genérico (barras simples) porque `Frame` no conoce la forma real del contenido de cada consumidor; componentes como `NotificationsPanel` exponen las mismas props (`isLoading`/`loadingType`/`loadingVariant`/`skeletonCount`/`scrollable` vía `maxHeight`) y simplemente las reenvían a su `Frame` interno en vez de reimplementar loading o scroll por su cuenta.
1166
1186
 
1167
1187
  ---
1168
1188
 
@@ -1281,7 +1301,31 @@ import { VscJson } from 'anteriority-ui/screens/components/Icons/vsc';
1281
1301
 
1282
1302
  **Packs disponibles:** cada pack de react-icons se expone en `screens/components/Icons/<pack>`, con el mismo nombre de carpeta que usa react-icons: `ai`, `bi`, `bs`, `cg`, `ci`, `di`, `fa`, `fa6`, `fc`, `fi`, `gi`, `go`, `gr`, `hi`, `hi2`, `im`, `io`, `io5`, `lia`, `lu`, `md`, `pi`, `ri`, `rx`, `si`, `sl`, `tb`, `tfi`, `ti`, `vsc`, `wi`.
1283
1303
 
1284
- **Notas:** Los nombres de cada ícono son exactamente los que exporta react-icons — usar el [buscador oficial](https://react-icons.github.io/react-icons/) o el componente [IconCatalog](#iconcatalog) para encontrar el nombre exacto dentro de su pack. `si` (Simple Icons) es el pack más relevante para logos de marcas/lenguajes/herramientas (JavaScript, YAML, Docker, etc.), pero **no tiene cobertura total** — por ejemplo no existe un ícono de C# en `si`; ese logo vive en el pack `tb` (`TbBrandCSharp`). Si un ícono no aparece en `si`, buscarlo en otros packs (`tb`, `fa`, `md`, etc.) antes de asumir que no está disponible. Packs como `fa` o `md` cubren además íconos genéricos de tipo de archivo (audio, pdf, imagen) que no tienen un logo de marca propio. Los archivos `screens/components/Icons/<pack>/index.js` son re-exports de 1 línea generados por `node scripts/generate-icon-packs.mjs` (correr de nuevo solo si react-icons agrega o quita un pack); quedan versionados en el repo como cualquier otro archivo fuente, no se regeneran en cada build.
1304
+ **Notas:** Los nombres de cada ícono son exactamente los que exporta react-icons — usar el [buscador oficial](https://react-icons.github.io/react-icons/), el componente [IconCatalog](#iconcatalog) o la función [findIcons](#findicons) para encontrar el nombre exacto dentro de su pack. `si` (Simple Icons) es el pack más relevante para logos de marcas/lenguajes/herramientas (JavaScript, YAML, Docker, etc.), pero **no tiene cobertura total** — por ejemplo no existe un ícono de C# en `si`; ese logo vive en los packs `tb` (`TbBrandCSharp`) y `pi` (`PiFileCSharp`). Si un ícono no aparece en `si`, buscarlo en otros packs antes de asumir que no está disponible — o usar `findIcons`, que ya busca en los 31 a la vez. Packs como `fa` o `md` cubren además íconos genéricos de tipo de archivo (audio, pdf, imagen) que no tienen un logo de marca propio. Los archivos `screens/components/Icons/<pack>/index.js` son re-exports de 1 línea generados por `node scripts/generate-icon-packs.mjs` (correr de nuevo solo si react-icons agrega o quita un pack); quedan versionados en el repo como cualquier otro archivo fuente, no se regeneran en cada build. `screens/components/Icons/registry.js` centraliza el import de los 31 packs (`ICON_PACKS`, `ICON_PACK_NAMES`) — tanto `IconCatalog` como `findIcons` lo reusan en vez de repetir la lista de imports.
1305
+
1306
+ ---
1307
+
1308
+ ### findIcons
1309
+
1310
+ Busca un término (substring, case-insensitive) en el nombre de ícono de **todos** los packs de react-icons a la vez, sin necesidad de saber de antemano en qué pack vive cada uno. Pensada para resolver casos como "necesito el logo de C#" sin tener que adivinar o probar pack por pack — `findIcons('csharp')` ya devuelve los matches de `tb`, `pi`, etc. en un solo llamado.
1311
+
1312
+ **Import:**
1313
+ ```jsx
1314
+ import { findIcons } from 'anteriority-ui/common/icons';
1315
+ ```
1316
+
1317
+ **Uso básico:**
1318
+ ```jsx
1319
+ const results = findIcons('csharp');
1320
+ // [{ pack: 'tb', name: 'TbBrandCSharp', Icon: [Function] }, { pack: 'pi', name: 'PiFileCSharp', Icon: [Function] }, ...]
1321
+
1322
+ const { Icon } = results[0];
1323
+ <Icon size={24} />
1324
+ ```
1325
+
1326
+ **Firma:** `findIcons(query: string) => { pack: string, name: string, Icon: ComponentType }[]`
1327
+
1328
+ **Notas:** Es una utilidad de **búsqueda en runtime**, no un import estático — devuelve el componente ya resuelto (`Icon`), listo para renderizar, pero no reemplaza el subpath import para un ícono que ya se conoce (ver [Icons (react-icons)](#icons-react-icons)), que sigue siendo más explícito y con mejor tree-shaking cuando el nombre exacto ya está decidido. Con query vacío devuelve `[]`. Puede devolver cientos de resultados con términos muy cortos/genéricos (ej. `'a'`) — conviene truncar/paginar en la UI que consuma el resultado, como hace [IconCatalog](#iconcatalog) internamente. También se puede usar directo desde `anteriority-ui/screens/components/Icons/findIcons` (el subpath real); `anteriority-ui/common/icons` es solo un alias más corto.
1285
1329
 
1286
1330
  ---
1287
1331
 
@@ -1931,6 +1975,107 @@ import ErrorMessage from 'screens/components/Notification/Error/ErrorMessage';
1931
1975
 
1932
1976
  ---
1933
1977
 
1978
+ ### NotificationsPanel
1979
+
1980
+ Panel de notificaciones construido sobre `Frame` (header con ícono de campana + título). Cada notificación se renderiza con `Callout` (título, mensaje en Markdown, timestamp, ícono, paleta por `variant`, botones de acción vía `Button`, botón de descarte (×) y soporte de click/navegación). La lista scrollea internamente (vía la prop `scrollable` de `Frame`, que a su vez usa `ScrollBar`) sin mostrar una barra de scroll nativa — el thumb queda oculto hasta hover/scroll. Soporta modo inline o modal (overlay + portal) y loading en dos variantes (spinner o skeleton), todo heredado de `Frame`.
1981
+
1982
+ **Import:**
1983
+ ```jsx
1984
+ import NotificationsPanel from '../NotificationsPanel/index'; // export default
1985
+ ```
1986
+
1987
+ **Uso básico:**
1988
+ ```jsx
1989
+ <NotificationsPanel
1990
+ notifications={[
1991
+ {
1992
+ id: 1,
1993
+ title: 'Nueva cita agendada - Firulais',
1994
+ message: 'Se agendó una cita para Firulais el 08/10/2026 a las 15:00 con el Dr. Doctor Doctor.',
1995
+ timestamp: 'Hace 5 minutos',
1996
+ actions: [
1997
+ { label: 'Confirmar asistencia', variant: 'primary', onClick: () => {} },
1998
+ { label: 'No podré asistir', variant: 'outline', color: 'red', onClick: () => {} },
1999
+ ],
2000
+ },
2001
+ ]}
2002
+ onDismiss={(id) => console.log('quitar', id)}
2003
+ />
2004
+ ```
2005
+
2006
+ **Props:**
2007
+
2008
+ | Prop | Tipo | Default | Descripción |
2009
+ |---|---|---|---|
2010
+ | `title` | string | `'Notificaciones'` | Título del header (prop `title` de `Frame`). |
2011
+ | `icon` | node | `<Bell size={20} />` | Ícono del header (prop `icon` de `Frame`). |
2012
+ | `notifications` | array | `[]` | Lista de notificaciones. Cada item acepta: `{ id, title?, message?, timestamp?, icon?, variant?, actions?, onClick?, href?, bgColor?, borderColor?, iconColor?, textColor? }`. `message` se pasa como `children` de `Callout` (acepta Markdown). `actions` es un array de `{ key?, label, variant?, color?, fullWidth?, onClick? }`, renderizados con `Button`. `variant` es la misma paleta de `Callout` (`'info'` \| `'warning'` \| `'danger'` \| `'success'` \| `'neutral'`, default `'neutral'`). `onClick`/`href` hacen la tarjeta entera clickeable/navegable (ver `Callout`). Los `bgColor`/`borderColor`/`iconColor`/`textColor` del item individual tienen prioridad sobre los equivalentes globales del panel (`itemBgColor`, etc.). |
2013
+ | `onDismiss` | `function(id)` | — | Si se define, cada tarjeta muestra un botón (×) que lo invoca con el `id` de la notificación. El panel no quita el item solo — el consumidor controla el array `notifications` (ver ejemplo `Dismissible` en Storybook). |
2014
+ | `emptyMessage` | string | `'No tenés notificaciones nuevas.'` | Texto mostrado cuando `notifications` está vacío. |
2015
+ | `maxHeight` | number | `380` | Alto máximo (px) del área scrolleable (pasa a `Frame` como `maxHeight`, junto con `scrollable` activado automáticamente cuando hay notificaciones). |
2016
+ | `dismissLabel` | string | `'Quitar notificación'` | `aria-label` del botón de descarte (pasa a `Callout` como `closeLabel`). |
2017
+ | `isModal` | boolean | `false` | Renderiza como overlay modal (pasa a `Frame`). |
2018
+ | `onClose` | function | — | Click en el overlay cuando `isModal` (pasa a `Frame`). |
2019
+ | `isLoading` | boolean | `false` | Muestra el overlay de carga de `Frame` (spinner o skeleton genérico) en vez de la lista. |
2020
+ | `loadingType` | `'spinner'` \| `'skeleton'` | `'spinner'` (default de `Frame`) | Tipo de loading cuando `isLoading` (pasa a `Frame`). |
2021
+ | `loadingVariant` | string | `'default'` (default de `Frame`) | Con `loadingType="spinner"`, variant de `Loading` (pasa a `Frame`). |
2022
+ | `skeletonCount` | number | `3` (default de `Frame`) | Con `loadingType="skeleton"`, cantidad de barras `Skeleton` (pasa a `Frame`). |
2023
+ | `className` | string | `''` | Clase adicional en el `Frame` raíz. |
2024
+ | `bgColor` | string | — | Color de fondo del panel (pasa a `Frame` como `bgColor`). |
2025
+ | `iconColor` | string | — | Color del ícono del header — la campana (pasa a `Frame` como `iconColor`). |
2026
+ | `itemBgColor` | string | — | Color de fondo por default de cada tarjeta (pasa a `Callout` como `bgColor`; cada item puede pisarlo con su propio `bgColor`). |
2027
+ | `itemBorderColor` | string | — | Color de borde por default de cada tarjeta (pasa a `Callout` como `borderColor`; pisable por item). |
2028
+ | `itemIconColor` | string | — | Color del ícono por default de cada tarjeta (pasa a `Callout` como `iconColor`; pisable por item). |
2029
+ | `textColor` | string | — | Color de texto (título + mensaje + timestamp) por default de cada tarjeta (pasa a `Callout`; pisable por item). |
2030
+
2031
+ **Ejemplos:**
2032
+ ```jsx
2033
+ // Vacío
2034
+ <NotificationsPanel notifications={[]} />
2035
+
2036
+ // Con descarte controlado por el consumidor
2037
+ function Example() {
2038
+ const [items, setItems] = useState(notifications);
2039
+ return (
2040
+ <NotificationsPanel
2041
+ notifications={items}
2042
+ onDismiss={(id) => setItems((prev) => prev.filter((n) => n.id !== id))}
2043
+ />
2044
+ );
2045
+ }
2046
+
2047
+ // Como modal
2048
+ <NotificationsPanel notifications={notifications} isModal onClose={() => setOpen(false)} />
2049
+
2050
+ // Loading tipo skeleton
2051
+ <NotificationsPanel notifications={[]} isLoading loadingType="skeleton" skeletonCount={4} />
2052
+
2053
+ // Notificación clickeable (navega al hacer click) y con paleta/colores propios
2054
+ <NotificationsPanel
2055
+ notifications={[
2056
+ {
2057
+ id: 1,
2058
+ variant: 'danger',
2059
+ title: 'Pago rechazado',
2060
+ message: 'No se pudo procesar el pago.',
2061
+ onClick: () => navigate('/facturas/00234'),
2062
+ },
2063
+ {
2064
+ id: 2,
2065
+ title: 'Con colores propios',
2066
+ message: 'Esta tarjeta define su propio bgColor/borderColor/textColor.',
2067
+ bgColor: '#ecfdf5',
2068
+ borderColor: '#a7f3d0',
2069
+ textColor: '#065f46',
2070
+ },
2071
+ ]}
2072
+ />
2073
+ ```
2074
+
2075
+ **Notas:** No persiste ni elimina notificaciones por su cuenta — es UI pura (ver regla de separación en `CLAUDE.md`). `onDismiss` solo informa el `id`; el array `notifications` sigue siendo responsabilidad de quien lo use. El contenedor interno aplica `overflow` vía `ScrollBar`, que oculta el track nativo del navegador y muestra un thumb delgado solo mientras se hace hover/scroll. Cada tarjeta es un `Callout` — para personalizar algo no cubierto por las props de `NotificationsPanel` (ej. `linkColor`, `markdownComponents`), usar `Callout` directamente en vez de este componente.
2076
+
2077
+ ---
2078
+
1934
2079
  ### PasswordResetPanel
1935
2080
 
1936
2081
  Panel deslizante (basado en `DrawPanel`) para que un administrador solicite y complete el restablecimiento de contraseña de un usuario, incluyendo lista de solicitudes pendientes, polling de estado y asignación de nueva clave.
@@ -2128,6 +2273,7 @@ import ScrollBar from 'screens/components/ScrollBar';
2128
2273
  | `maxHeight` | `string` \| `number` | — | Altura máxima del área scrolleable. |
2129
2274
  | `hideHorizontalTrack` | `boolean` | `false` | Oculta el thumb/track horizontal propio (el scroll sigue funcionando). Lo usa `TableA` cuando muestra su pie de desplazamiento. |
2130
2275
  | `hideVerticalTrack` | `boolean` | `false` | Oculta el thumb/track vertical propio (el scroll sigue funcionando). Lo usa `TableA`/`TableB` con `stickyHeader`. |
2276
+ | `reserveTrackSpace` | `boolean` | `false` | Reserva `padding-right` en el área de scroll para que el thumb vertical no quede superpuesto sobre el contenido (texto/botones). Pensado para listas verticales de tarjetas (lo usa `Frame` cuando `scrollable`); no afecta el ancho de tablas con scroll horizontal porque es opt-in. |
2131
2277
  | `scrollAreaRef` | `ref` \| `function` | — | Recibe el elemento del área de scroll interna, para poder leer o mover `scrollLeft`/`scrollTop` desde afuera. |
2132
2278
  | `className` | `string` | `''` | Clase adicional para el contenedor raíz. |
2133
2279
  | `contentClassName` | `string` | `''` | Clase adicional para el área de scroll interna. |
@@ -2146,7 +2292,7 @@ import ScrollBar from 'screens/components/ScrollBar';
2146
2292
 
2147
2293
  ### Sidebar
2148
2294
 
2149
- Barra lateral de navegación colapsable con grupos de items, badges por método HTTP, menú de usuario, menú contextual por item (editar/eliminar/fijar/colaboradores) y reordenamiento por drag & drop.
2295
+ Barra lateral de navegación colapsable con grupos de items, subitems anidados y colapsables (ej. "Vacunas" → Ver/Crear/Pendientes/Lotes), badges por método HTTP, menú de usuario, menú contextual por item (editar/eliminar/fijar/colaboradores) y reordenamiento por drag & drop.
2150
2296
 
2151
2297
  **Import:**
2152
2298
  ```jsx
@@ -2168,8 +2314,8 @@ import Sidebar from 'screens/components/Sidebar/index';
2168
2314
 
2169
2315
  | Prop | Tipo | Default | Descripción |
2170
2316
  |---|---|---|---|
2171
- | `groups` | `Array<{ title?, items: Array<{ id, label, icon?, badge?, badgeType?, pinned?, isOwner?, isShared? }> }>` | — | Estructura de grupos e items del menú. |
2172
- | `activeId` | `string`\|`number` | — | Id del item actualmente activo. |
2317
+ | `groups` | `Array<{ title?, items: Array<{ id, label, icon?, badge?, badgeType?, pinned?, isOwner?, isShared?, subItems? }> }>` | — | Estructura de grupos e items del menú. `subItems` es un array opcional de `{ id, label, icon?, badge?, badgeType? }`: si un item lo tiene, se renderiza como header colapsable (con chevron) en vez de item navegable — el click solo expande/colapsa, no dispara `onSelect` ni se marca "activo" (ver Notas). |
2318
+ | `activeId` | `string`\|`number` | — | Id del item (o subitem) actualmente activo. Si matchea el id de un subitem, ese subitem se resalta y su grupo padre se auto-expande al montar el componente. |
2173
2319
  | `onSelect` | `function(item)` | — | Callback al seleccionar un item. |
2174
2320
  | `collapsible` | `boolean` | `true` | Muestra el botón de colapsar y habilita el atajo de teclado para colapsar/expandir. |
2175
2321
  | `loading` | `boolean` | `false` | Muestra `skeletonItems` filas de `Skeleton` en vez de los grupos reales. |
@@ -2192,6 +2338,32 @@ import Sidebar from 'screens/components/Sidebar/index';
2192
2338
 
2193
2339
  **Ejemplos:**
2194
2340
  ```jsx
2341
+ // Item con subItems colapsables (ej. "Vacunas")
2342
+ <Sidebar
2343
+ groups={[
2344
+ {
2345
+ title: 'General',
2346
+ items: [
2347
+ { id: 'home', label: 'Inicio', icon: Home },
2348
+ {
2349
+ id: 'vaccines',
2350
+ label: 'Vacunas',
2351
+ icon: Activity,
2352
+ subItems: [
2353
+ { id: 'vaccines-view', label: 'Ver vacunas' },
2354
+ { id: 'vaccines-create', label: 'Crear vacuna' },
2355
+ { id: 'vaccines-pending', label: 'Vacunas pendientes' },
2356
+ { id: 'vaccines-batches', label: 'Lotes de vacunas' },
2357
+ ],
2358
+ },
2359
+ ],
2360
+ },
2361
+ ]}
2362
+ activeId="vaccines-view"
2363
+ onSelect={(item) => setActiveId(item.id)}
2364
+ user={{ email: 'jane.doe@example.com' }}
2365
+ />
2366
+
2195
2367
  <Sidebar groups={[]} loading skeletonItems={8} user={{ email: 'jane.doe@example.com' }} />
2196
2368
 
2197
2369
  <Sidebar
@@ -2215,7 +2387,7 @@ import Sidebar from 'screens/components/Sidebar/index';
2215
2387
  />
2216
2388
  ```
2217
2389
 
2218
- **Notas:** Persiste el estado colapsado en `localStorage` bajo la clave `sidebar_collapsed`.
2390
+ **Notas:** Persiste el estado colapsado en `localStorage` bajo la clave `sidebar_collapsed`. Los subitems no soportan drag & drop (`onReorderItems`), menú contextual (`onItemEdit`/`onItemDelete`/`onItemPin`/`onItemCollaborators`) ni colapsado individual persistido — solo navegación simple vía `onSelect`. Cuando el sidebar está colapsado (`isCollapsed`), los grupos con `subItems` se muestran colapsados y sin chevron (igual que cualquier item simple, con tooltip); hay que expandir el sidebar para ver/navegar los subitems. `onSearch`/`filterMenuGroups` matchea por `label` del item padre o de cualquiera de sus `subItems`, pero si matchea, se muestra el item completo con **todos** sus subitems (no solo el que matcheó).
2219
2391
 
2220
2392
  ---
2221
2393
 
@@ -0,0 +1,7 @@
1
+ import { findIcons } from "../../screens/components/Icons/findIcons";
2
+ import { ICON_PACKS, ICON_PACK_NAMES } from "../../screens/components/Icons/registry";
3
+ export {
4
+ ICON_PACKS,
5
+ ICON_PACK_NAMES,
6
+ findIcons
7
+ };