@africanies/angular-web-sdk 0.1.7 → 0.1.9

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.
Files changed (157) hide show
  1. package/.angular/cache/22.0.9/playground/.tsbuildinfo +1 -1
  2. package/.nx/cache/{4082520855419352840 → 15302120112746851896}/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +30 -8
  3. package/.nx/cache/15302120112746851896/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -0
  4. package/.nx/cache/{4082520855419352840 → 15302120112746851896}/dist/libs/africanies-theme/tailwind-preset.cjs +89 -12
  5. package/.nx/cache/{4082520855419352840 → 15302120112746851896}/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +14 -4
  6. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +4639 -0
  7. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -0
  8. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +2748 -0
  9. package/.nx/cache/18393832731522001121/dist/libs/africanies-core/README.md +85 -0
  10. package/.nx/cache/{17491371155400585077 → 18393832731522001121}/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +139 -51
  11. package/.nx/cache/18393832731522001121/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -0
  12. package/.nx/cache/18393832731522001121/dist/libs/africanies-core/package.json +31 -0
  13. package/.nx/cache/{17491371155400585077 → 18393832731522001121}/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +14 -11
  14. package/.nx/cache/{3314955264390318442 → 3953970051755345887}/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +775 -566
  15. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  16. package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +150 -119
  17. package/.nx/cache/{12276756491762927325 → 4225324930345532464}/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +34 -6
  18. package/.nx/cache/4225324930345532464/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -0
  19. package/.nx/cache/{12276756491762927325 → 4225324930345532464}/dist/libs/africanies-theme/tailwind-preset.cjs +104 -19
  20. package/.nx/cache/{12276756491762927325 → 4225324930345532464}/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +21 -2
  21. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/README.md +69 -0
  22. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/assets/icons.sprite.svg +1851 -0
  23. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs +833 -0
  24. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs.map +1 -0
  25. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/package.json +30 -0
  26. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/types/africanies-africanies-icons.d.ts +108 -0
  27. package/.nx/cache/877026836685850193/dist/libs/africanies-models/README.md +60 -0
  28. package/.nx/cache/877026836685850193/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs +613 -0
  29. package/.nx/cache/877026836685850193/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs.map +1 -0
  30. package/.nx/cache/877026836685850193/dist/libs/africanies-models/package.json +23 -0
  31. package/.nx/cache/877026836685850193/dist/libs/africanies-models/types/africanies-africanies-models.d.ts +1452 -0
  32. package/.nx/cache/{17499961385238663835 → 9406544675747598148}/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +765 -615
  33. package/.nx/cache/9406544675747598148/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  34. package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +170 -130
  35. package/.nx/cache/run.json +36 -36
  36. package/.nx/cache/terminalOutputs/{3017748574492080228 → 12565224028063674529} +16 -7
  37. package/.nx/cache/terminalOutputs/{8648260256613557249 → 13478445034325959094} +175 -172
  38. package/.nx/cache/terminalOutputs/{7126777490381568487 → 15149466893522345489} +10 -5
  39. package/.nx/cache/terminalOutputs/{4082520855419352840 → 15302120112746851896} +1 -1
  40. package/.nx/cache/terminalOutputs/15835338512221847842 +185 -0
  41. package/.nx/cache/terminalOutputs/{17491371155400585077 → 17289624717336768573} +1 -1
  42. package/.nx/cache/terminalOutputs/17571479482482478257 +35 -0
  43. package/.nx/cache/terminalOutputs/{9408529484792607601 → 17960163310445607219} +1 -1
  44. package/.nx/cache/terminalOutputs/18393832731522001121 +21 -0
  45. package/.nx/cache/terminalOutputs/3088264223473143358 +8 -0
  46. package/.nx/cache/terminalOutputs/{3314955264390318442 → 3953970051755345887} +1 -1
  47. package/.nx/cache/terminalOutputs/{12276756491762927325 → 4225324930345532464} +1 -1
  48. package/.nx/cache/terminalOutputs/4363587767476846758 +176 -0
  49. package/.nx/cache/terminalOutputs/5151121439688073678 +21 -0
  50. package/.nx/cache/terminalOutputs/7462873189468732296 +17 -0
  51. package/.nx/cache/terminalOutputs/8032148447904822353 +184 -182
  52. package/.nx/cache/terminalOutputs/8193462679279018087 +39 -0
  53. package/.nx/cache/terminalOutputs/877026836685850193 +21 -0
  54. package/.nx/cache/terminalOutputs/9406544675747598148 +21 -0
  55. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db +0 -0
  56. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-shm +0 -0
  57. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-wal +0 -0
  58. package/.nx/workspace-data/d/daemon.log +6813 -0
  59. package/.nx/workspace-data/d/server-process.json +2 -2
  60. package/.nx/workspace-data/file-map.json +3490 -3432
  61. package/.nx/workspace-data/nx_files.nxt +0 -0
  62. package/.nx/workspace-data/project-graph.json +7 -2
  63. package/apps/playground/src/app/pages/tokens-page.ts +17 -13
  64. package/apps/playground/src/app/snippets/filters.snippets.ts +10 -0
  65. package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +6 -1
  66. package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -1
  67. package/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +6 -1
  68. package/dist/libs/africanies-icons/assets/icons.sprite.svg +7 -0
  69. package/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs +1 -0
  70. package/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs.map +1 -1
  71. package/dist/libs/africanies-icons/types/africanies-africanies-icons.d.ts +2 -2
  72. package/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs +22 -8
  73. package/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs.map +1 -1
  74. package/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +22 -10
  75. package/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -1
  76. package/dist/libs/africanies-theme/tailwind-preset.cjs +84 -16
  77. package/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +4 -2
  78. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +723 -618
  79. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -1
  80. package/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +142 -114
  81. package/libs/africanies-core/src/index.ts +1 -1
  82. package/libs/africanies-core/src/lib/notification/notification-link.ts +6 -1
  83. package/libs/africanies-icons/src/assets/icons.sprite.svg +7 -0
  84. package/libs/africanies-icons/src/lib/icon-name.ts +1 -0
  85. package/libs/africanies-models/src/lib/filters/configs/seed-configs.ts +22 -8
  86. package/libs/africanies-theme/src/lib/mode-color.safelist.ts +12 -0
  87. package/libs/africanies-theme/src/lib/mode-color.service.spec.ts +55 -0
  88. package/libs/africanies-theme/src/lib/mode-color.service.ts +11 -11
  89. package/libs/africanies-theme/tailwind-preset.cjs +84 -16
  90. package/libs/africanies-ui/src/lib/action-menu/action-menu.component.ts +3 -3
  91. package/libs/africanies-ui/src/lib/alert/alert.component.ts +4 -4
  92. package/libs/africanies-ui/src/lib/avatar/avatar-menu.component.ts +2 -2
  93. package/libs/africanies-ui/src/lib/button/button.component.spec.ts +7 -7
  94. package/libs/africanies-ui/src/lib/button/button.component.ts +5 -5
  95. package/libs/africanies-ui/src/lib/chip/chip.component.ts +4 -4
  96. package/libs/africanies-ui/src/lib/content-stack/content-stack.component.ts +1 -1
  97. package/libs/africanies-ui/src/lib/feedback/error-indicator.component.ts +3 -3
  98. package/libs/africanies-ui/src/lib/feedback/error-state.component.ts +2 -2
  99. package/libs/africanies-ui/src/lib/filters/filter-drawer.panel.ts +44 -27
  100. package/libs/africanies-ui/src/lib/forms/checkbox/checkbox.component.ts +2 -1
  101. package/libs/africanies-ui/src/lib/forms/file-upload/file-preview.dialog.ts +1 -1
  102. package/libs/africanies-ui/src/lib/forms/file-upload/file-upload.component.ts +1 -1
  103. package/libs/africanies-ui/src/lib/forms/form-field.classes.ts +36 -6
  104. package/libs/africanies-ui/src/lib/forms/otp-input/otp-input.component.ts +2 -1
  105. package/libs/africanies-ui/src/lib/forms/radio/radio.component.ts +2 -1
  106. package/libs/africanies-ui/src/lib/forms/select/select.component.ts +1 -1
  107. package/libs/africanies-ui/src/lib/forms/toggle/toggle.component.ts +1 -1
  108. package/libs/africanies-ui/src/lib/image/image.component.ts +1 -1
  109. package/libs/africanies-ui/src/lib/info-popover/info-popover.component.ts +2 -2
  110. package/libs/africanies-ui/src/lib/layout/app-shell-content-header.component.ts +1 -1
  111. package/libs/africanies-ui/src/lib/layout/app-shell-header.component.ts +14 -14
  112. package/libs/africanies-ui/src/lib/layout/app-shell.component.html +2 -2
  113. package/libs/africanies-ui/src/lib/layout/app-shell.component.ts +3 -3
  114. package/libs/africanies-ui/src/lib/layout/header-greeting.util.ts +0 -2
  115. package/libs/africanies-ui/src/lib/layout/header-weather.spec.ts +11 -40
  116. package/libs/africanies-ui/src/lib/layout/header-weather.ts +22 -63
  117. package/libs/africanies-ui/src/lib/navigation/shipping-mode-switch/shipping-mode-switch.component.ts +6 -6
  118. package/libs/africanies-ui/src/lib/navigation/side-nav/africanies-side-nav.component.html +21 -13
  119. package/libs/africanies-ui/src/lib/navigation/side-nav/africanies-side-nav.component.ts +13 -7
  120. package/libs/africanies-ui/src/lib/notifications/notification-drawer.panel.ts +6 -6
  121. package/libs/africanies-ui/src/lib/overlay/confirm-dialog.component.ts +2 -2
  122. package/libs/africanies-ui/src/lib/overlay/drawer.service.ts +2 -2
  123. package/libs/africanies-ui/src/lib/overlay/modal.service.ts +3 -3
  124. package/libs/africanies-ui/src/lib/overlay/overlay-frame.component.ts +1 -1
  125. package/libs/africanies-ui/src/lib/pagination/pagination.component.ts +1 -1
  126. package/libs/africanies-ui/src/lib/stepper/stepper.component.ts +7 -7
  127. package/libs/africanies-ui/src/lib/table/table-column.ts +9 -0
  128. package/libs/africanies-ui/src/lib/table/table.component.spec.ts +99 -0
  129. package/libs/africanies-ui/src/lib/table/table.component.ts +72 -30
  130. package/libs/africanies-ui/src/lib/toast/toast-item.component.ts +6 -6
  131. package/package.json +1 -1
  132. package/svg/hand-coins.svg +9 -0
  133. package/.nx/cache/12276756491762927325/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +0 -1
  134. package/.nx/cache/17491371155400585077/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +0 -1
  135. package/.nx/cache/17499961385238663835/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +0 -1
  136. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +0 -1
  137. package/.nx/cache/4082520855419352840/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +0 -1
  138. package/.nx/cache/terminalOutputs/17499961385238663835 +0 -21
  139. /package/.nx/cache/{12276756491762927325 → 15302120112746851896}/dist/libs/africanies-theme/README.md +0 -0
  140. /package/.nx/cache/{12276756491762927325 → 15302120112746851896}/dist/libs/africanies-theme/package.json +0 -0
  141. /package/.nx/cache/{17491371155400585077 → 17289624717336768573}/dist/libs/africanies-core/README.md +0 -0
  142. /package/.nx/cache/{17491371155400585077 → 17289624717336768573}/dist/libs/africanies-core/package.json +0 -0
  143. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/README.md +0 -0
  144. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  145. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  146. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +0 -0
  147. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/assets/carriers/dhl.svg +0 -0
  148. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/package.json +0 -0
  149. /package/.nx/cache/{4082520855419352840 → 4225324930345532464}/dist/libs/africanies-theme/README.md +0 -0
  150. /package/.nx/cache/{4082520855419352840 → 4225324930345532464}/dist/libs/africanies-theme/package.json +0 -0
  151. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/README.md +0 -0
  152. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  153. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  154. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +0 -0
  155. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/assets/carriers/dhl.svg +0 -0
  156. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/package.json +0 -0
  157. /package/.nx/cache/terminalOutputs/{16279119293212733518 → 14005735098959303198} +0 -0
@@ -92,7 +92,7 @@ class ButtonComponent {
92
92
  */
93
93
  hostClass = computed(() => {
94
94
  const base = 'relative inline-flex items-center justify-center gap-2 box-border font-sans font-medium rounded-md border transition-colors cursor-pointer ' +
95
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink ' +
95
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus ' +
96
96
  'disabled:opacity-50 disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:cursor-not-allowed';
97
97
  const sizes = {
98
98
  sm: 'text-body-sm px-2.5 py-0 h-8',
@@ -108,16 +108,19 @@ class ButtonComponent {
108
108
  variantClass = this.modeColor.classes().ghostPrimary;
109
109
  }
110
110
  else if (variant === 'secondary') {
111
+ variantClass = this.modeColor.classes().stroked;
112
+ }
113
+ else if (variant === 'flat') {
111
114
  variantClass =
112
- 'bg-white dark:bg-ink-950 text-ink dark:text-white border-border dark:border-white/15 hover:bg-background-welcome dark:hover:bg-white/10';
115
+ 'bg-surface dark:bg-ink-950 text-ink dark:text-white border-control dark:border-white/15 hover:bg-background-hover dark:hover:bg-white/10';
113
116
  }
114
117
  else if (variant === 'ghost') {
115
118
  variantClass =
116
- 'bg-transparent text-ink dark:text-white border-transparent hover:bg-background-welcome dark:hover:bg-white/10';
119
+ 'bg-transparent text-ink dark:text-white border-transparent hover:bg-background-hover dark:hover:bg-white/10';
117
120
  }
118
121
  else if (variant === 'ghost-danger') {
119
122
  variantClass =
120
- 'bg-transparent text-danger border-transparent hover:bg-danger-subtle dark:hover:bg-danger/10';
123
+ 'bg-transparent text-danger-dark border-transparent hover:bg-danger-subtle dark:text-danger dark:hover:bg-danger/10';
121
124
  }
122
125
  else if (variant === 'underline') {
123
126
  variantClass =
@@ -125,7 +128,7 @@ class ButtonComponent {
125
128
  }
126
129
  else {
127
130
  variantClass =
128
- 'bg-danger text-white border-transparent hover:bg-danger-dark';
131
+ 'bg-danger-dark text-white border-transparent hover:bg-danger-strong dark:bg-danger dark:hover:bg-danger-dark';
129
132
  }
130
133
  return `${base} ${sizes[this.size()]} ${variantClass}`;
131
134
  }, /* @ts-ignore */
@@ -268,20 +271,20 @@ class ToastItemComponent {
268
271
  return v === 'warning' || v === 'danger' ? 'assertive' : 'polite';
269
272
  }, /* @ts-ignore */
270
273
  ...(ngDevMode ? [{ debugName: "a11yLive" }] : /* istanbul ignore next */ []));
271
- shellClass = computed(() => 'africanies-toast-enter relative flex w-full overflow-hidden rounded-lg border border-border ' +
272
- 'bg-white/95 text-ink shadow-lg backdrop-blur-md ' +
273
- 'dark:border-white/15 dark:bg-ink/95 dark:text-white', /* @ts-ignore */
274
+ shellClass = computed(() => 'africanies-toast-enter relative flex w-full overflow-hidden rounded-panel border border-border ' +
275
+ 'bg-surface/95 text-ink shadow-floating backdrop-blur-md ' +
276
+ 'dark:border-white/15 dark:bg-ink-950/95 dark:text-white', /* @ts-ignore */
274
277
  ...(ngDevMode ? [{ debugName: "shellClass" }] : /* istanbul ignore next */ []));
275
278
  accentClass = computed(() => `w-1 shrink-0 self-stretch ${accentFill(this.item().variant)}`, /* @ts-ignore */
276
279
  ...(ngDevMode ? [{ debugName: "accentClass" }] : /* istanbul ignore next */ []));
277
280
  iconClass = computed(() => {
278
281
  switch (this.item().variant) {
279
282
  case 'success':
280
- return 'text-export dark:text-export-light';
283
+ return 'text-export-strong dark:text-export-light';
281
284
  case 'warning':
282
285
  return 'text-warning-dark dark:text-warning';
283
286
  case 'danger':
284
- return 'text-danger';
287
+ return 'text-danger-dark dark:text-danger';
285
288
  default:
286
289
  return 'text-ink-blue dark:text-neutral-300';
287
290
  }
@@ -291,11 +294,11 @@ class ToastItemComponent {
291
294
  const base = 'm-0 break-words text-body-sm font-semibold';
292
295
  switch (this.item().variant) {
293
296
  case 'success':
294
- return `${base} text-export dark:text-export-light`;
297
+ return `${base} text-export-strong dark:text-export-light`;
295
298
  case 'warning':
296
299
  return `${base} text-warning-dark dark:text-warning`;
297
300
  case 'danger':
298
- return `${base} text-danger`;
301
+ return `${base} text-danger-dark dark:text-danger`;
299
302
  default:
300
303
  return `${base} text-ink dark:text-white`;
301
304
  }
@@ -681,14 +684,14 @@ class ToastHostComponent {
681
684
  <div class="flex w-full flex-col gap-2">
682
685
  @if (toastService.showHostActions()) {
683
686
  <div
684
- class="sticky top-0 z-10 flex flex-wrap justify-end gap-2 bg-white/80 pb-1 backdrop-blur-sm dark:bg-ink/80"
687
+ class="sticky top-0 z-10 flex flex-wrap justify-end gap-2 bg-white/80 pb-1 backdrop-blur-sm dark:bg-ink-surface/80"
685
688
  >
686
689
  @if (toastService.hasStacks()) {
687
690
  @if (toastService.allStacksExpanded()) {
688
691
  <button
689
692
  africanies-button
690
693
  type="button"
691
- variant="secondary"
694
+ variant="flat"
692
695
  size="sm"
693
696
  class="!min-h-0 !rounded-full !bg-white !px-2.5 !py-1 !text-caption dark:!bg-ink"
694
697
  (click)="toastService.collapseAll()"
@@ -699,7 +702,7 @@ class ToastHostComponent {
699
702
  <button
700
703
  africanies-button
701
704
  type="button"
702
- variant="secondary"
705
+ variant="flat"
703
706
  size="sm"
704
707
  class="!min-h-0 !rounded-full !bg-white !px-2.5 !py-1 !text-caption dark:!bg-ink"
705
708
  (click)="toastService.expandAll()"
@@ -711,7 +714,7 @@ class ToastHostComponent {
711
714
  <button
712
715
  africanies-button
713
716
  type="button"
714
- variant="secondary"
717
+ variant="flat"
715
718
  size="sm"
716
719
  class="!min-h-0 !rounded-full !bg-white !px-2.5 !py-1 !text-caption dark:!bg-ink"
717
720
  (click)="toastService.clear()"
@@ -750,14 +753,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
750
753
  <div class="flex w-full flex-col gap-2">
751
754
  @if (toastService.showHostActions()) {
752
755
  <div
753
- class="sticky top-0 z-10 flex flex-wrap justify-end gap-2 bg-white/80 pb-1 backdrop-blur-sm dark:bg-ink/80"
756
+ class="sticky top-0 z-10 flex flex-wrap justify-end gap-2 bg-white/80 pb-1 backdrop-blur-sm dark:bg-ink-surface/80"
754
757
  >
755
758
  @if (toastService.hasStacks()) {
756
759
  @if (toastService.allStacksExpanded()) {
757
760
  <button
758
761
  africanies-button
759
762
  type="button"
760
- variant="secondary"
763
+ variant="flat"
761
764
  size="sm"
762
765
  class="!min-h-0 !rounded-full !bg-white !px-2.5 !py-1 !text-caption dark:!bg-ink"
763
766
  (click)="toastService.collapseAll()"
@@ -768,7 +771,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
768
771
  <button
769
772
  africanies-button
770
773
  type="button"
771
- variant="secondary"
774
+ variant="flat"
772
775
  size="sm"
773
776
  class="!min-h-0 !rounded-full !bg-white !px-2.5 !py-1 !text-caption dark:!bg-ink"
774
777
  (click)="toastService.expandAll()"
@@ -780,7 +783,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
780
783
  <button
781
784
  africanies-button
782
785
  type="button"
783
- variant="secondary"
786
+ variant="flat"
784
787
  size="sm"
785
788
  class="!min-h-0 !rounded-full !bg-white !px-2.5 !py-1 !text-caption dark:!bg-ink"
786
789
  (click)="toastService.clear()"
@@ -1501,9 +1504,9 @@ class ActionMenuComponent {
1501
1504
  const active = this.activeIndex() === index;
1502
1505
  const base = 'flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-body-sm no-underline transition-colors ' +
1503
1506
  'disabled:cursor-not-allowed disabled:opacity-50 ' +
1504
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-ink';
1507
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus';
1505
1508
  if (item.danger) {
1506
- return (`${base} text-danger ` +
1509
+ return (`${base} text-danger-dark dark:text-danger ` +
1507
1510
  (active
1508
1511
  ? 'bg-danger-subtle dark:bg-danger/15'
1509
1512
  : 'hover:bg-danger-subtle dark:hover:bg-danger/15'));
@@ -1707,7 +1710,7 @@ class ActionMenuComponent {
1707
1710
  >
1708
1711
  <div
1709
1712
  #panel
1710
- class="min-w-44 rounded-md border border-border bg-white py-1 shadow-lg outline-none dark:border-white/15 dark:bg-ink-950"
1713
+ class="min-w-44 rounded-md border border-border bg-surface py-1 shadow-floating outline-none dark:border-white/15 dark:bg-ink-950"
1711
1714
  role="menu"
1712
1715
  tabindex="-1"
1713
1716
  [attr.aria-label]="ariaLabel()"
@@ -1824,7 +1827,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1824
1827
  >
1825
1828
  <div
1826
1829
  #panel
1827
- class="min-w-44 rounded-md border border-border bg-white py-1 shadow-lg outline-none dark:border-white/15 dark:bg-ink-950"
1830
+ class="min-w-44 rounded-md border border-border bg-surface py-1 shadow-floating outline-none dark:border-white/15 dark:bg-ink-950"
1828
1831
  role="menu"
1829
1832
  tabindex="-1"
1830
1833
  [attr.aria-label]="ariaLabel()"
@@ -1927,7 +1930,7 @@ class EmptyStateComponent {
1927
1930
  <button
1928
1931
  africanies-button
1929
1932
  type="button"
1930
- variant="secondary"
1933
+ variant="flat"
1931
1934
  [disabled]="refreshing()"
1932
1935
  (click)="retry.emit()"
1933
1936
  >
@@ -1958,7 +1961,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1958
1961
  <button
1959
1962
  africanies-button
1960
1963
  type="button"
1961
- variant="secondary"
1964
+ variant="flat"
1962
1965
  [disabled]="refreshing()"
1963
1966
  (click)="retry.emit()"
1964
1967
  >
@@ -2053,10 +2056,10 @@ class ErrorIndicatorComponent {
2053
2056
  aria-hidden="true"
2054
2057
  >
2055
2058
  <span
2056
- class="africanies-error-indicator-dot-ping absolute inset-0 rounded-full bg-red-400 opacity-75"
2059
+ class="africanies-error-indicator-dot-ping absolute inset-0 rounded-full bg-danger-dark opacity-75"
2057
2060
  ></span>
2058
2061
  <span
2059
- class="africanies-error-indicator-dot-core relative size-3 rounded-full bg-red-500"
2062
+ class="africanies-error-indicator-dot-core relative size-3 rounded-full bg-danger-dark"
2060
2063
  ></span>
2061
2064
  </span>
2062
2065
 
@@ -2071,7 +2074,7 @@ class ErrorIndicatorComponent {
2071
2074
  type="button"
2072
2075
  variant="ghost"
2073
2076
  size="sm"
2074
- class="africanies-error-indicator-retry shrink-0 !min-h-0 !border-transparent !bg-transparent !px-1.5 !py-0.5 !text-danger hover:!bg-danger/10 dark:hover:!bg-danger/20"
2077
+ class="africanies-error-indicator-retry shrink-0 !min-h-0 !border-transparent !bg-transparent !px-1.5 !py-0.5 !text-danger-dark hover:!bg-danger/10 dark:hover:!bg-danger/20"
2075
2078
  [class.opacity-50]="disabled()"
2076
2079
  [disabled]="disabled()"
2077
2080
  (click)="onRetry()"
@@ -2098,10 +2101,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2098
2101
  aria-hidden="true"
2099
2102
  >
2100
2103
  <span
2101
- class="africanies-error-indicator-dot-ping absolute inset-0 rounded-full bg-red-400 opacity-75"
2104
+ class="africanies-error-indicator-dot-ping absolute inset-0 rounded-full bg-danger-dark opacity-75"
2102
2105
  ></span>
2103
2106
  <span
2104
- class="africanies-error-indicator-dot-core relative size-3 rounded-full bg-red-500"
2107
+ class="africanies-error-indicator-dot-core relative size-3 rounded-full bg-danger-dark"
2105
2108
  ></span>
2106
2109
  </span>
2107
2110
 
@@ -2116,7 +2119,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2116
2119
  type="button"
2117
2120
  variant="ghost"
2118
2121
  size="sm"
2119
- class="africanies-error-indicator-retry shrink-0 !min-h-0 !border-transparent !bg-transparent !px-1.5 !py-0.5 !text-danger hover:!bg-danger/10 dark:hover:!bg-danger/20"
2122
+ class="africanies-error-indicator-retry shrink-0 !min-h-0 !border-transparent !bg-transparent !px-1.5 !py-0.5 !text-danger-dark hover:!bg-danger/10 dark:hover:!bg-danger/20"
2120
2123
  [class.opacity-50]="disabled()"
2121
2124
  [disabled]="disabled()"
2122
2125
  (click)="onRetry()"
@@ -2167,12 +2170,12 @@ class ErrorStateComponent {
2167
2170
  class="flex flex-col items-center justify-center gap-3 py-10 px-4 text-center text-ink dark:text-white"
2168
2171
  role="alert"
2169
2172
  >
2170
- <africanies-icon name="warning" [size]="32" class="text-danger" />
2171
- <p class="m-0 text-body text-danger max-w-md">{{ message() }}</p>
2173
+ <africanies-icon name="warning" [size]="32" class="text-danger-dark dark:text-danger" />
2174
+ <p class="m-0 text-body text-danger-dark max-w-md dark:text-danger">{{ message() }}</p>
2172
2175
  <button
2173
2176
  africanies-button
2174
2177
  type="button"
2175
- variant="secondary"
2178
+ variant="flat"
2176
2179
  [disabled]="refreshing()"
2177
2180
  (click)="retry.emit()"
2178
2181
  >
@@ -2198,12 +2201,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2198
2201
  class="flex flex-col items-center justify-center gap-3 py-10 px-4 text-center text-ink dark:text-white"
2199
2202
  role="alert"
2200
2203
  >
2201
- <africanies-icon name="warning" [size]="32" class="text-danger" />
2202
- <p class="m-0 text-body text-danger max-w-md">{{ message() }}</p>
2204
+ <africanies-icon name="warning" [size]="32" class="text-danger-dark dark:text-danger" />
2205
+ <p class="m-0 text-body text-danger-dark max-w-md dark:text-danger">{{ message() }}</p>
2203
2206
  <button
2204
2207
  africanies-button
2205
2208
  type="button"
2206
- variant="secondary"
2209
+ variant="flat"
2207
2210
  [disabled]="refreshing()"
2208
2211
  (click)="retry.emit()"
2209
2212
  >
@@ -2736,11 +2739,11 @@ class AlertComponent {
2736
2739
  iconClass = computed(() => {
2737
2740
  switch (this.variant()) {
2738
2741
  case 'success':
2739
- return 'text-export dark:text-export-light';
2742
+ return 'text-export-strong dark:text-export-light';
2740
2743
  case 'warning':
2741
2744
  return 'text-warning-dark dark:text-warning';
2742
2745
  case 'danger':
2743
- return 'text-danger';
2746
+ return 'text-danger-dark dark:text-danger';
2744
2747
  default:
2745
2748
  return 'text-ink-blue dark:text-neutral-300';
2746
2749
  }
@@ -2750,11 +2753,11 @@ class AlertComponent {
2750
2753
  const base = 'm-0 text-body font-medium';
2751
2754
  switch (this.variant()) {
2752
2755
  case 'success':
2753
- return `${base} text-export dark:text-export-light`;
2756
+ return `${base} text-export-strong dark:text-export-light`;
2754
2757
  case 'warning':
2755
2758
  return `${base} text-warning-dark dark:text-warning`;
2756
2759
  case 'danger':
2757
- return `${base} text-danger`;
2760
+ return `${base} text-danger-dark dark:text-danger`;
2758
2761
  default:
2759
2762
  return `${base} text-ink dark:text-white`;
2760
2763
  }
@@ -2907,13 +2910,13 @@ class ChipComponent {
2907
2910
  switch (this.variant()) {
2908
2911
  case 'success':
2909
2912
  case 'export':
2910
- return 'bg-export-subtle text-export dark:bg-export/15 dark:text-export-light';
2913
+ return 'bg-export-subtle text-export-strong dark:bg-export/15 dark:text-export-light';
2911
2914
  case 'import':
2912
- return 'bg-import-subtle text-import dark:bg-import/15 dark:text-import-light';
2915
+ return 'bg-import-subtle text-import-strong dark:bg-import/15 dark:text-import-light';
2913
2916
  case 'warning':
2914
2917
  return 'bg-warning-subtle text-warning-dark dark:bg-warning/15 dark:text-warning';
2915
2918
  case 'danger':
2916
- return 'bg-danger-subtle text-danger dark:bg-danger/15 dark:text-danger';
2919
+ return 'bg-danger-subtle text-danger-dark dark:bg-danger/15 dark:text-danger';
2917
2920
  default:
2918
2921
  return 'bg-border/60 text-ink dark:bg-white/10 dark:text-neutral-200';
2919
2922
  }
@@ -2934,7 +2937,7 @@ class ChipComponent {
2934
2937
  @if (removable()) {
2935
2938
  <button
2936
2939
  type="button"
2937
- class="inline-flex shrink-0 cursor-pointer items-center justify-center rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ink"
2940
+ class="inline-flex shrink-0 cursor-pointer items-center justify-center rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus"
2938
2941
  [attr.aria-label]="removeLabel()"
2939
2942
  (click)="onRemove($event)"
2940
2943
  >
@@ -2962,7 +2965,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2962
2965
  @if (removable()) {
2963
2966
  <button
2964
2967
  type="button"
2965
- class="inline-flex shrink-0 cursor-pointer items-center justify-center rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ink"
2968
+ class="inline-flex shrink-0 cursor-pointer items-center justify-center rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus"
2966
2969
  [attr.aria-label]="removeLabel()"
2967
2970
  (click)="onRemove($event)"
2968
2971
  >
@@ -3177,7 +3180,7 @@ class ContentStackComponent {
3177
3180
  </p>
3178
3181
  }
3179
3182
  @if (extraLine()) {
3180
- <p class="m-0 text-caption text-neutral-500 dark:text-neutral-400">
3183
+ <p class="m-0 text-caption text-neutral-600 dark:text-neutral-400">
3181
3184
  {{ extraLine() }}
3182
3185
  </p>
3183
3186
  }
@@ -3206,7 +3209,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
3206
3209
  </p>
3207
3210
  }
3208
3211
  @if (extraLine()) {
3209
- <p class="m-0 text-caption text-neutral-500 dark:text-neutral-400">
3212
+ <p class="m-0 text-caption text-neutral-600 dark:text-neutral-400">
3210
3213
  {{ extraLine() }}
3211
3214
  </p>
3212
3215
  }
@@ -3779,8 +3782,8 @@ class InfoPopoverComponent {
3779
3782
  * Bound via `[class]` so arrow position tracks preferred placement.
3780
3783
  */
3781
3784
  panelArrowClass = computed(() => {
3782
- const card = 'relative z-10 w-max max-w-xs rounded-lg border border-border bg-white px-4 py-3 text-ink shadow-lg dark:border-white/15 dark:bg-ink-950 dark:text-white';
3783
- const base = "before:pointer-events-none before:absolute before:z-0 before:h-2.5 before:w-2.5 before:rotate-45 before:border before:border-border before:bg-white before:content-[''] dark:before:border-white/15 dark:before:bg-ink-950";
3785
+ const card = 'relative z-10 w-max max-w-xs rounded-panel border border-border bg-surface px-4 py-3 text-ink shadow-floating dark:border-white/15 dark:bg-ink-950 dark:text-white';
3786
+ const base = "before:pointer-events-none before:absolute before:z-0 before:h-2.5 before:w-2.5 before:rotate-45 before:border before:border-border before:bg-surface before:content-[''] dark:before:border-white/15 dark:before:bg-ink-950";
3784
3787
  switch (this.placement()) {
3785
3788
  case 'bottom':
3786
3789
  return `${card} ${base} before:left-1/2 before:top-0 before:-translate-x-1/2 before:-translate-y-1/2 before:border-b-0 before:border-r-0`;
@@ -4080,22 +4083,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4080
4083
  * scanner must see full utility names in this package so they survive purge
4081
4084
  * when consumers scan the published UI bundle.
4082
4085
  */
4086
+ /**
4087
+ * Inset focus ring — stays inside the control border box.
4088
+ *
4089
+ * Prefer this over `outline` + `outline-offset`. Those paint *outside* the box
4090
+ * and are clipped by `overflow: hidden|clip|auto` ancestors (modals, drawers,
4091
+ * steppers, scroll panes). `box-sizing` does not affect outlines.
4092
+ */
4093
+ const FORM_FOCUS_WITHIN_CLASS = 'focus-within:outline-none focus-within:ring-2 focus-within:ring-inset focus-within:ring-focus';
4094
+ /** Same inset ring for host / button-style focus-visible. */
4095
+ const FORM_FOCUS_VISIBLE_CLASS = 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus';
4096
+ /** Peer-driven inset ring (checkbox / radio control chrome). */
4097
+ const FORM_FOCUS_VISIBLE_PEER_CLASS = 'peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-inset peer-focus-visible:ring-focus';
4098
+ /** Direct focus (OTP cells and similar). */
4099
+ const FORM_FOCUS_CLASS = 'focus:outline-none focus:ring-2 focus:ring-inset focus:ring-focus';
4083
4100
  /** Label above the control. Hidden when empty so callers can omit a label. */
4084
4101
  const FORM_LABEL_CLASS = 'block text-body-sm font-medium text-ink dark:text-white mb-1.5 empty:hidden';
4085
- /** Default control shell — `h-10` matches md `africanies-button`. */
4086
- const FORM_FIELD_CLASS = 'flex items-center w-full h-10 box-border rounded-md border border-neutral-300 bg-white dark:bg-ink-950 text-ink dark:text-white dark:border-white/25 ' +
4087
- 'focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ink';
4088
4102
  /**
4089
- * Error border`!` so it wins over shell `border-neutral-300` /
4103
+ * Default control shell `h-10` matches md `africanies-button`.
4104
+ *
4105
+ * Focus utilities are inlined (not concatenated) so Tailwind content scanning
4106
+ * always sees the full class names in this string literal.
4107
+ */
4108
+ const FORM_FIELD_CLASS = 'flex items-center w-full h-10 box-border rounded-control border border-control bg-surface dark:bg-ink-950 text-ink dark:text-white dark:border-white/25 ' +
4109
+ 'focus-within:outline-none focus-within:ring-2 focus-within:ring-inset focus-within:ring-focus';
4110
+ /**
4111
+ * Error border — `!` so it wins over shell `border-border` /
4090
4112
  * `dark:border-white/25` (same specificity; those utilities sort later in the
4091
4113
  * generated CSS and otherwise paint over `border-danger`).
4092
4114
  *
4115
+ * Use `danger` (`#ff001c`) for both border and focus ring so field chrome
4116
+ * matches validation copy (not the darker accessible `danger-dark` text token).
4117
+ *
4093
4118
  * Keep these literals static for Tailwind content scanning in consumer apps.
4094
4119
  */
4095
- const FORM_FIELD_ERROR_CLASS = '!border-danger dark:!border-danger focus-within:!outline-danger';
4120
+ const FORM_FIELD_ERROR_CLASS = '!border-danger dark:!border-danger focus-within:!ring-danger focus:!ring-danger';
4096
4121
  /** Native input / textarea inside a shell. */
4097
4122
  const FORM_CONTROL_INNER_CLASS = 'flex-1 min-w-0 min-h-0 h-full w-full bg-transparent border-0 outline-none text-body text-ink dark:text-white px-3 py-0 ' +
4098
- 'placeholder:text-neutral-400 disabled:cursor-not-allowed';
4123
+ 'placeholder:text-neutral-500 disabled:cursor-not-allowed';
4099
4124
  /** Prefix / suffix projection gutters. Hidden when nothing is projected. */
4100
4125
  const FORM_AFFIX_CLASS = 'inline-flex items-center shrink-0 px-2.5 text-neutral-600 dark:text-neutral-400 empty:hidden';
4101
4126
  /**
@@ -4110,7 +4135,7 @@ const FORM_DATE_INNER_CLASS = FORM_CONTROL_INNER_CLASS +
4110
4135
  ' cursor-pointer [&::-webkit-calendar-picker-indicator]:cursor-pointer';
4111
4136
  /** Hint under the control (hidden when error is shown). */
4112
4137
  const FORM_HINT_CLASS = 'mt-1.5 text-caption text-neutral-600 dark:text-neutral-400 m-0';
4113
- /** Field-level validation message (not ErrorStateComponent). */
4138
+ /** Field-level validation message (not ErrorStateComponent). Matches `#ff001c`. */
4114
4139
  const FORM_ERROR_CLASS = 'mt-1.5 text-caption text-danger m-0';
4115
4140
  /** Disabled shell — dimmed with not-allowed cursor over the full field chrome. */
4116
4141
  const FORM_DISABLED_CLASS = 'opacity-50 cursor-not-allowed';
@@ -4341,229 +4366,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4341
4366
  }]
4342
4367
  }], propDecorators: { dateInput: [{ type: i0.ViewChild, args: ['dateInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
4343
4368
 
4344
- let nextRadioId = 0;
4345
- /**
4346
- * Radio group following the shared AFRICANIES form field pattern.
4347
- *
4348
- * **Prefix/suffix slots are omitted** — radios are mutually exclusive options
4349
- * with labels; affix projection does not apply.
4350
- *
4351
- * Selected ring and center disc follow {@link ModeColorService}
4352
- * (SFN green / STN orange).
4353
- *
4354
- * @typeParam T - Option value type.
4355
- *
4356
- * @example
4357
- * ```html
4358
- * <africanies-radio
4359
- * label="Shipment mode"
4360
- * [options]="modeOptions"
4361
- * [(value)]="mode"
4362
- * />
4363
- * ```
4364
- */
4365
- class RadioComponent {
4366
- modeColor = inject(ModeColorService);
4367
- groupName = `africanies-radio-${++nextRadioId}`;
4368
- hintId = `${this.groupName}-hint`;
4369
- errorId = `${this.groupName}-error`;
4370
- labelClass = FORM_LABEL_CLASS;
4371
- hintClass = FORM_HINT_CLASS;
4372
- errorClass = FORM_ERROR_CLASS;
4373
- /** Group legend / label. */
4374
- label = input('', /* @ts-ignore */
4375
- ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
4376
- /** Helper copy. Hidden while {@link error} is set. */
4377
- hint = input(undefined, /* @ts-ignore */
4378
- ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
4379
- /** Field-level validation message. Distinct from `ErrorStateComponent`. */
4380
- error = input(null, /* @ts-ignore */
4381
- ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
4382
- /** Options rendered as radios. */
4383
- options = input([], /* @ts-ignore */
4384
- ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
4385
- /** Selected option value (`[(value)]` + CVA). */
4386
- value = model(null, /* @ts-ignore */
4387
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4388
- /** Host disable flag (in addition to CVA). */
4389
- disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled',
4390
- transform: booleanAttribute });
4391
- cvaDisabled = signal(false, /* @ts-ignore */
4392
- ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
4393
- disabled = computed(() => this.disabledInput() || this.cvaDisabled(), /* @ts-ignore */
4394
- ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4395
- describedBy = computed(() => {
4396
- if (this.error()) {
4397
- return this.errorId;
4398
- }
4399
- if (this.hint()) {
4400
- return this.hintId;
4401
- }
4402
- return null;
4403
- }, /* @ts-ignore */
4404
- ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
4405
- onChange = () => undefined;
4406
- onTouched = () => undefined;
4407
- trackOption(opt) {
4408
- return String(opt.value);
4409
- }
4410
- isSelected(opt) {
4411
- return Object.is(this.value(), opt.value);
4412
- }
4413
- markClass(opt) {
4414
- const base = 'pointer-events-none flex size-4 items-center justify-center rounded-full border-2 bg-transparent transition-colors ' +
4415
- 'peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ink';
4416
- const err = this.error() ? ' !border-danger dark:!border-danger' : '';
4417
- if (this.isSelected(opt)) {
4418
- // Ring uses mode accent; fill stays transparent so the center dot reads clearly.
4419
- return `${base} ${this.modeColor.classes().border}${err}`;
4420
- }
4421
- return `${base} border-neutral-400 dark:border-white${err}`;
4422
- }
4423
- /** Selected inner disc — SFN green / STN orange. */
4424
- dotClass = computed(() => `block size-1.5 rounded-full ${this.modeColor.classes().bg}`, /* @ts-ignore */
4425
- ...(ngDevMode ? [{ debugName: "dotClass" }] : /* istanbul ignore next */ []));
4426
- /** @param value - Selected option value or `null`. */
4427
- writeValue(value) {
4428
- this.value.set(value);
4429
- }
4430
- /** @param fn - Selection change callback. */
4431
- registerOnChange(fn) {
4432
- this.onChange = fn;
4433
- }
4434
- /** @param fn - Blur callback. */
4435
- registerOnTouched(fn) {
4436
- this.onTouched = fn;
4437
- }
4438
- /** @param isDisabled - Blocks interaction when true. */
4439
- setDisabledState(isDisabled) {
4440
- this.cvaDisabled.set(isDisabled);
4441
- }
4442
- select(opt) {
4443
- if (this.disabled() || opt.disabled) {
4444
- return;
4445
- }
4446
- this.value.set(opt.value);
4447
- this.onChange(opt.value);
4448
- }
4449
- onBlur() {
4450
- this.onTouched();
4451
- }
4452
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RadioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4453
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: RadioComponent, isStandalone: true, selector: "africanies-radio", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { classAttribute: "inline-flex max-w-full" }, providers: [
4454
- {
4455
- provide: NG_VALUE_ACCESSOR,
4456
- useExisting: forwardRef(() => RadioComponent),
4457
- multi: true,
4458
- },
4459
- ], ngImport: i0, template: `
4460
- <fieldset
4461
- class="m-0 p-0 border-0 min-w-0"
4462
- [attr.aria-invalid]="error() ? true : null"
4463
- [attr.aria-describedby]="describedBy()"
4464
- >
4465
- <legend [class]="labelClass">{{ label() }}</legend>
4466
- <div class="flex flex-row flex-wrap gap-x-4 gap-y-2" role="radiogroup">
4467
- @for (opt of options(); track trackOption(opt)) {
4468
- <label
4469
- class="inline-flex items-center gap-2.5 text-body text-ink dark:text-white"
4470
- [class.opacity-50]="disabled() || opt.disabled"
4471
- [class.cursor-pointer]="!(disabled() || opt.disabled)"
4472
- [class.cursor-not-allowed]="disabled() || opt.disabled"
4473
- >
4474
- <span class="relative inline-flex size-4 shrink-0">
4475
- <input
4476
- type="radio"
4477
- class="peer absolute inset-0 z-10 m-0 size-full opacity-0"
4478
- [class.cursor-pointer]="!(disabled() || opt.disabled)"
4479
- [class.cursor-not-allowed]="disabled() || opt.disabled"
4480
- [name]="groupName"
4481
- [checked]="isSelected(opt)"
4482
- [disabled]="disabled() || !!opt.disabled"
4483
- (change)="select(opt)"
4484
- (blur)="onBlur()"
4485
- />
4486
- <span [class]="markClass(opt)" aria-hidden="true">
4487
- @if (isSelected(opt)) {
4488
- <span [class]="dotClass()"></span>
4489
- }
4490
- </span>
4491
- </span>
4492
- <span>{{ opt.label }}</span>
4493
- </label>
4494
- }
4495
- </div>
4496
- @if (error(); as err) {
4497
- <p [id]="errorId" [class]="errorClass" role="alert">{{ err }}</p>
4498
- } @else if (hint(); as h) {
4499
- <p [id]="hintId" [class]="hintClass">{{ h }}</p>
4500
- }
4501
- </fieldset>
4502
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
4503
- }
4504
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RadioComponent, decorators: [{
4505
- type: Component,
4506
- args: [{
4507
- selector: 'africanies-radio',
4508
- standalone: true,
4509
- changeDetection: ChangeDetectionStrategy.OnPush,
4510
- host: {
4511
- class: 'inline-flex max-w-full',
4512
- },
4513
- providers: [
4514
- {
4515
- provide: NG_VALUE_ACCESSOR,
4516
- useExisting: forwardRef(() => RadioComponent),
4517
- multi: true,
4518
- },
4519
- ],
4520
- template: `
4521
- <fieldset
4522
- class="m-0 p-0 border-0 min-w-0"
4523
- [attr.aria-invalid]="error() ? true : null"
4524
- [attr.aria-describedby]="describedBy()"
4525
- >
4526
- <legend [class]="labelClass">{{ label() }}</legend>
4527
- <div class="flex flex-row flex-wrap gap-x-4 gap-y-2" role="radiogroup">
4528
- @for (opt of options(); track trackOption(opt)) {
4529
- <label
4530
- class="inline-flex items-center gap-2.5 text-body text-ink dark:text-white"
4531
- [class.opacity-50]="disabled() || opt.disabled"
4532
- [class.cursor-pointer]="!(disabled() || opt.disabled)"
4533
- [class.cursor-not-allowed]="disabled() || opt.disabled"
4534
- >
4535
- <span class="relative inline-flex size-4 shrink-0">
4536
- <input
4537
- type="radio"
4538
- class="peer absolute inset-0 z-10 m-0 size-full opacity-0"
4539
- [class.cursor-pointer]="!(disabled() || opt.disabled)"
4540
- [class.cursor-not-allowed]="disabled() || opt.disabled"
4541
- [name]="groupName"
4542
- [checked]="isSelected(opt)"
4543
- [disabled]="disabled() || !!opt.disabled"
4544
- (change)="select(opt)"
4545
- (blur)="onBlur()"
4546
- />
4547
- <span [class]="markClass(opt)" aria-hidden="true">
4548
- @if (isSelected(opt)) {
4549
- <span [class]="dotClass()"></span>
4550
- }
4551
- </span>
4552
- </span>
4553
- <span>{{ opt.label }}</span>
4554
- </label>
4555
- }
4556
- </div>
4557
- @if (error(); as err) {
4558
- <p [id]="errorId" [class]="errorClass" role="alert">{{ err }}</p>
4559
- } @else if (hint(); as h) {
4560
- <p [id]="hintId" [class]="hintClass">{{ h }}</p>
4561
- }
4562
- </fieldset>
4563
- `,
4564
- }]
4565
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
4566
-
4567
4369
  /**
4568
4370
  * Tailwind panel classes for each {@link ModalSize}.
4569
4371
  *
@@ -4873,16 +4675,16 @@ class ModalService {
4873
4675
  'africanies-overlay-backdrop',
4874
4676
  'bg-ink/45',
4875
4677
  'backdrop-blur-sm',
4876
- 'dark:bg-ink/60',
4678
+ 'dark:bg-ink-950/60',
4877
4679
  ],
4878
4680
  panelClass: [
4879
4681
  'africanies-modal-panel',
4880
- 'bg-white',
4682
+ 'bg-surface',
4881
4683
  'dark:bg-ink-950',
4882
4684
  'text-ink',
4883
4685
  'dark:text-white',
4884
- 'rounded-lg',
4885
- 'shadow-xl',
4686
+ 'rounded-panel',
4687
+ 'shadow-floating',
4886
4688
  'border',
4887
4689
  'border-border',
4888
4690
  'dark:border-white/15',
@@ -5687,7 +5489,7 @@ class SelectComponent {
5687
5489
  @if (multiple() && selectedList().length) {
5688
5490
  @for (chip of selectedList(); track trackOption(chip)) {
5689
5491
  <span
5690
- class="inline-flex items-center gap-1 rounded-md border border-neutral-300 bg-background-welcome dark:border-white/20 dark:bg-ink text-body-sm text-ink dark:text-white px-2 py-0.5"
5492
+ class="inline-flex items-center gap-1 rounded-md border border-neutral-300 bg-background-welcome dark:border-white/20 dark:bg-ink-950 text-body-sm text-ink dark:text-white px-2 py-0.5"
5691
5493
  >
5692
5494
  @if (chip.prefix; as prefixIcon) {
5693
5495
  <africanies-icon [name]="prefixIcon" [size]="14" class="shrink-0" />
@@ -5806,7 +5608,7 @@ class SelectComponent {
5806
5608
  <input
5807
5609
  #searchInput
5808
5610
  type="search"
5809
- class="w-full rounded-md border border-neutral-300 dark:border-white/25 bg-transparent text-body text-ink dark:text-white px-2.5 py-1.5 outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink"
5611
+ class="w-full rounded-md border border-neutral-300 dark:border-white/25 bg-transparent text-body text-ink dark:text-white px-2.5 py-1.5 outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ink"
5810
5612
  [value]="searchQuery()"
5811
5613
  placeholder="Search…"
5812
5614
  (input)="onSearchInput($event)"
@@ -6015,7 +5817,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
6015
5817
  @if (multiple() && selectedList().length) {
6016
5818
  @for (chip of selectedList(); track trackOption(chip)) {
6017
5819
  <span
6018
- class="inline-flex items-center gap-1 rounded-md border border-neutral-300 bg-background-welcome dark:border-white/20 dark:bg-ink text-body-sm text-ink dark:text-white px-2 py-0.5"
5820
+ class="inline-flex items-center gap-1 rounded-md border border-neutral-300 bg-background-welcome dark:border-white/20 dark:bg-ink-950 text-body-sm text-ink dark:text-white px-2 py-0.5"
6019
5821
  >
6020
5822
  @if (chip.prefix; as prefixIcon) {
6021
5823
  <africanies-icon [name]="prefixIcon" [size]="14" class="shrink-0" />
@@ -6134,7 +5936,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
6134
5936
  <input
6135
5937
  #searchInput
6136
5938
  type="search"
6137
- class="w-full rounded-md border border-neutral-300 dark:border-white/25 bg-transparent text-body text-ink dark:text-white px-2.5 py-1.5 outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink"
5939
+ class="w-full rounded-md border border-neutral-300 dark:border-white/25 bg-transparent text-body text-ink dark:text-white px-2.5 py-1.5 outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ink"
6138
5940
  [value]="searchQuery()"
6139
5941
  placeholder="Search…"
6140
5942
  (input)="onSearchInput($event)"
@@ -6801,13 +6603,6 @@ class FilterDrawerPanel {
6801
6603
  const opt = Array.isArray(selected) ? selected[0] : selected;
6802
6604
  this.draft.update((s) => ({ ...s, order: opt?.value }));
6803
6605
  }
6804
- onRadioChange(key, value) {
6805
- if (value == null) {
6806
- this.clearField(key);
6807
- return;
6808
- }
6809
- this.setFieldValue(key, value);
6810
- }
6811
6606
  onSelectChange(key, selected) {
6812
6607
  const opt = Array.isArray(selected) ? selected[0] : selected;
6813
6608
  if (!opt) {
@@ -6948,11 +6743,18 @@ class FilterDrawerPanel {
6948
6743
  value,
6949
6744
  });
6950
6745
  }
6951
- toRadioOptions(options) {
6952
- return (options ?? []).map((o) => ({
6953
- label: o.label,
6954
- value: o.value,
6955
- }));
6746
+ /**
6747
+ * Boolean Yes/No chips. Prefer field.options when present; otherwise default
6748
+ * colored Yes/No so every boolean filter stays visually distinct.
6749
+ */
6750
+ booleanOptions(field) {
6751
+ if (field.options?.length) {
6752
+ return field.options;
6753
+ }
6754
+ return [
6755
+ { value: '1', label: 'Yes', color: '#25945c' },
6756
+ { value: '0', label: 'No', color: '#FF001C' },
6757
+ ];
6956
6758
  }
6957
6759
  onReset() {
6958
6760
  const next = resetFilterState(true, this.draft());
@@ -7201,23 +7003,48 @@ class FilterDrawerPanel {
7201
7003
  </div>
7202
7004
  }
7203
7005
  @case ('boolean') {
7204
- <africanies-radio
7205
- label=""
7206
- [options]="toRadioOptions(field.options)"
7207
- [value]="draft().values[field.key] ?? null"
7208
- (valueChange)="onRadioChange(field.key, $event)"
7209
- />
7210
- }
7211
- @case ('select') {
7212
- <!-- Label already shown in the section header — avoid double label. -->
7213
- <africanies-select
7214
- label=""
7215
- [searchable]="true"
7216
- [placeholder]="field.placeholder ?? 'Select…'"
7217
- [options]="selectOptionsFor(field)"
7218
- [selected]="selectedForField(field)"
7219
- [error]="optionErrorFor(field.key)"
7220
- [loading]="optionLoadingFor(field.key)"
7006
+ <div class="flex flex-wrap gap-2">
7007
+ @for (opt of booleanOptions(field); track opt.value) {
7008
+ <button
7009
+ type="button"
7010
+ class="cursor-pointer rounded-md border px-2.5 py-1.5 text-body-sm transition-colors"
7011
+ [style.color]="
7012
+ draft().values[field.key] === opt.value
7013
+ ? opt.color ?? null
7014
+ : null
7015
+ "
7016
+ [style.border-color]="
7017
+ draft().values[field.key] === opt.value
7018
+ ? opt.color ?? null
7019
+ : null
7020
+ "
7021
+ [style.background-color]="
7022
+ draft().values[field.key] === opt.value && opt.color
7023
+ ? opt.color + '1A'
7024
+ : null
7025
+ "
7026
+ [class]="
7027
+ draft().values[field.key] === opt.value
7028
+ ? 'font-medium'
7029
+ : 'border-neutral-300 text-neutral-600 hover:border-neutral-400 dark:border-white/15 dark:text-neutral-300'
7030
+ "
7031
+ (click)="setFieldValue(field.key, opt.value)"
7032
+ >
7033
+ {{ opt.label }}
7034
+ </button>
7035
+ }
7036
+ </div>
7037
+ }
7038
+ @case ('select') {
7039
+ <!-- Label already shown in the section header — avoid double label. -->
7040
+ <africanies-select
7041
+ label=""
7042
+ [searchable]="true"
7043
+ [placeholder]="field.placeholder ?? 'Select…'"
7044
+ [options]="selectOptionsFor(field)"
7045
+ [selected]="selectedForField(field)"
7046
+ [error]="optionErrorFor(field.key)"
7047
+ [loading]="optionLoadingFor(field.key)"
7221
7048
  [retrying]="optionLoadingFor(field.key)"
7222
7049
  [showRetry]="true"
7223
7050
  (retry)="retryFieldOptions(field.key)"
@@ -7265,7 +7092,7 @@ class FilterDrawerPanel {
7265
7092
  type="button"
7266
7093
  variant="ghost"
7267
7094
  size="sm"
7268
- class="shrink-0 !min-h-0 self-start !px-1 !py-0 text-neutral-500 hover:text-ink dark:text-neutral-400 dark:hover:text-white"
7095
+ class="shrink-0 !min-h-0 self-start !px-1 !py-0 text-neutral-600 hover:text-ink dark:text-neutral-400 dark:hover:text-white"
7269
7096
  aria-label="Dismiss"
7270
7097
  (click)="applyError.set(null)"
7271
7098
  >
@@ -7297,7 +7124,7 @@ class FilterDrawerPanel {
7297
7124
  </div>
7298
7125
  </div>
7299
7126
  </div>
7300
- `, isInline: true, dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: ButtonComponent, selector: "button[africanies-button], a[africanies-button]", inputs: ["variant", "size", "disabled", "loading"] }, { kind: "component", type: DatePickerComponent, selector: "africanies-date-picker", inputs: ["label", "hint", "error", "min", "max", "value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: RadioComponent, selector: "africanies-radio", inputs: ["label", "hint", "error", "options", "value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: SelectComponent, selector: "africanies-select", inputs: ["label", "hint", "error", "showRetry", "retryLabel", "retryingLabel", "retrying", "loading", "loadingLabel", "placeholder", "options", "selected", "searchable", "allowFreeText", "multiple", "maxSelected", "create", "disabled", "showTriggerIcon", "size"], outputs: ["retry", "searchChange", "optionsChange", "selectedChange"] }, { kind: "component", type: TextInputComponent, selector: "africanies-text-input", inputs: ["label", "hint", "error", "placeholder", "type", "autocomplete", "value", "disabled"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7127
+ `, isInline: true, dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: ButtonComponent, selector: "button[africanies-button], a[africanies-button]", inputs: ["variant", "size", "disabled", "loading"] }, { kind: "component", type: DatePickerComponent, selector: "africanies-date-picker", inputs: ["label", "hint", "error", "min", "max", "value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: SelectComponent, selector: "africanies-select", inputs: ["label", "hint", "error", "showRetry", "retryLabel", "retryingLabel", "retrying", "loading", "loadingLabel", "placeholder", "options", "selected", "searchable", "allowFreeText", "multiple", "maxSelected", "create", "disabled", "showTriggerIcon", "size"], outputs: ["retry", "searchChange", "optionsChange", "selectedChange"] }, { kind: "component", type: TextInputComponent, selector: "africanies-text-input", inputs: ["label", "hint", "error", "placeholder", "type", "autocomplete", "value", "disabled"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7301
7128
  }
7302
7129
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FilterDrawerPanel, decorators: [{
7303
7130
  type: Component,
@@ -7312,7 +7139,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7312
7139
  AfricaniesIconComponent,
7313
7140
  ButtonComponent,
7314
7141
  DatePickerComponent,
7315
- RadioComponent,
7316
7142
  SelectComponent,
7317
7143
  TextInputComponent,
7318
7144
  ],
@@ -7470,12 +7296,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7470
7296
  </div>
7471
7297
  }
7472
7298
  @case ('boolean') {
7473
- <africanies-radio
7474
- label=""
7475
- [options]="toRadioOptions(field.options)"
7476
- [value]="draft().values[field.key] ?? null"
7477
- (valueChange)="onRadioChange(field.key, $event)"
7478
- />
7299
+ <div class="flex flex-wrap gap-2">
7300
+ @for (opt of booleanOptions(field); track opt.value) {
7301
+ <button
7302
+ type="button"
7303
+ class="cursor-pointer rounded-md border px-2.5 py-1.5 text-body-sm transition-colors"
7304
+ [style.color]="
7305
+ draft().values[field.key] === opt.value
7306
+ ? opt.color ?? null
7307
+ : null
7308
+ "
7309
+ [style.border-color]="
7310
+ draft().values[field.key] === opt.value
7311
+ ? opt.color ?? null
7312
+ : null
7313
+ "
7314
+ [style.background-color]="
7315
+ draft().values[field.key] === opt.value && opt.color
7316
+ ? opt.color + '1A'
7317
+ : null
7318
+ "
7319
+ [class]="
7320
+ draft().values[field.key] === opt.value
7321
+ ? 'font-medium'
7322
+ : 'border-neutral-300 text-neutral-600 hover:border-neutral-400 dark:border-white/15 dark:text-neutral-300'
7323
+ "
7324
+ (click)="setFieldValue(field.key, opt.value)"
7325
+ >
7326
+ {{ opt.label }}
7327
+ </button>
7328
+ }
7329
+ </div>
7479
7330
  }
7480
7331
  @case ('select') {
7481
7332
  <!-- Label already shown in the section header — avoid double label. -->
@@ -7534,7 +7385,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7534
7385
  type="button"
7535
7386
  variant="ghost"
7536
7387
  size="sm"
7537
- class="shrink-0 !min-h-0 self-start !px-1 !py-0 text-neutral-500 hover:text-ink dark:text-neutral-400 dark:hover:text-white"
7388
+ class="shrink-0 !min-h-0 self-start !px-1 !py-0 text-neutral-600 hover:text-ink dark:text-neutral-400 dark:hover:text-white"
7538
7389
  aria-label="Dismiss"
7539
7390
  (click)="applyError.set(null)"
7540
7391
  >
@@ -7610,15 +7461,15 @@ class DrawerService {
7610
7461
  'africanies-overlay-backdrop',
7611
7462
  'bg-ink/45',
7612
7463
  'backdrop-blur-sm',
7613
- 'dark:bg-ink/60',
7464
+ 'dark:bg-ink-950/60',
7614
7465
  ],
7615
7466
  panelClass: [
7616
7467
  'africanies-drawer-panel',
7617
- 'bg-white',
7468
+ 'bg-surface',
7618
7469
  'dark:bg-ink-950',
7619
7470
  'text-ink',
7620
7471
  'dark:text-white',
7621
- 'shadow-xl',
7472
+ 'shadow-floating',
7622
7473
  'border-l',
7623
7474
  'border-border',
7624
7475
  'dark:border-white/15',
@@ -7751,8 +7602,8 @@ class ConfirmDialogComponent {
7751
7602
  iconWellClass = computed(() => {
7752
7603
  const well = 'inline-flex size-14 shrink-0 items-center justify-center rounded-2xl';
7753
7604
  return this.options.danger
7754
- ? `${well} bg-danger-subtle text-danger dark:bg-danger/20 dark:text-danger`
7755
- : `${well} bg-export-subtle text-export dark:bg-export/20 dark:text-export-light`;
7605
+ ? `${well} bg-danger-subtle text-danger-dark dark:bg-danger/20 dark:text-danger`
7606
+ : `${well} bg-export-subtle text-export-strong dark:bg-export/20 dark:text-export-light`;
7756
7607
  }, /* @ts-ignore */
7757
7608
  ...(ngDevMode ? [{ debugName: "iconWellClass" }] : /* istanbul ignore next */ []));
7758
7609
  constructor() {
@@ -8138,7 +7989,7 @@ class OverlayFrameComponent {
8138
7989
  <ng-content select="[africaniesOverlayHeader]" />
8139
7990
  </div>
8140
7991
  <div
8141
- class="africanies-overlay-scroll min-h-0 flex-1 overflow-x-clip overflow-y-auto px-[2px] -mx-[2px]"
7992
+ class="africanies-overlay-scroll min-h-0 flex-1 overflow-x-clip overflow-y-auto px-1 -mx-1"
8142
7993
  >
8143
7994
  <ng-content />
8144
7995
  </div>
@@ -8161,7 +8012,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
8161
8012
  <ng-content select="[africaniesOverlayHeader]" />
8162
8013
  </div>
8163
8014
  <div
8164
- class="africanies-overlay-scroll min-h-0 flex-1 overflow-x-clip overflow-y-auto px-[2px] -mx-[2px]"
8015
+ class="africanies-overlay-scroll min-h-0 flex-1 overflow-x-clip overflow-y-auto px-1 -mx-1"
8165
8016
  >
8166
8017
  <ng-content />
8167
8018
  </div>
@@ -9053,7 +8904,7 @@ class CheckboxComponent {
9053
8904
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
9054
8905
  markClass = computed(() => {
9055
8906
  const base = 'pointer-events-none flex size-4 items-center justify-center rounded border text-white transition-colors ' +
9056
- 'peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ink';
8907
+ FORM_FOCUS_VISIBLE_PEER_CLASS;
9057
8908
  const err = this.error() ? ' !border-danger dark:!border-danger' : '';
9058
8909
  if (this.value()) {
9059
8910
  const c = this.modeColor.classes();
@@ -9722,13 +9573,13 @@ class FilePreviewDialogComponent {
9722
9573
  <img
9723
9574
  [src]="src()"
9724
9575
  [alt]="data.file.name"
9725
- class="max-h-[min(70vh,40rem)] w-full rounded-md object-contain bg-background-welcome ring-1 ring-border dark:bg-ink dark:ring-white/15"
9576
+ class="max-h-[min(70vh,40rem)] w-full rounded-md object-contain bg-background-welcome ring-1 ring-border dark:bg-ink-950 dark:ring-white/15"
9726
9577
  />
9727
9578
  } @else {
9728
9579
  <div
9729
9580
  class="flex flex-col items-center gap-3 rounded-md bg-background-welcome px-6 py-10 text-center ring-1 ring-border dark:bg-white/5 dark:ring-white/15"
9730
9581
  >
9731
- <africanies-icon name="eye-slash" [size]="32" class="text-neutral-500" />
9582
+ <africanies-icon name="eye-slash" [size]="32" class="text-neutral-600" />
9732
9583
  <p class="m-0 text-body font-medium text-ink dark:text-white">
9733
9584
  Preview not available
9734
9585
  </p>
@@ -9792,13 +9643,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
9792
9643
  <img
9793
9644
  [src]="src()"
9794
9645
  [alt]="data.file.name"
9795
- class="max-h-[min(70vh,40rem)] w-full rounded-md object-contain bg-background-welcome ring-1 ring-border dark:bg-ink dark:ring-white/15"
9646
+ class="max-h-[min(70vh,40rem)] w-full rounded-md object-contain bg-background-welcome ring-1 ring-border dark:bg-ink-950 dark:ring-white/15"
9796
9647
  />
9797
9648
  } @else {
9798
9649
  <div
9799
9650
  class="flex flex-col items-center gap-3 rounded-md bg-background-welcome px-6 py-10 text-center ring-1 ring-border dark:bg-white/5 dark:ring-white/15"
9800
9651
  >
9801
- <africanies-icon name="eye-slash" [size]="32" class="text-neutral-500" />
9652
+ <africanies-icon name="eye-slash" [size]="32" class="text-neutral-600" />
9802
9653
  <p class="m-0 text-body font-medium text-ink dark:text-white">
9803
9654
  Preview not available
9804
9655
  </p>
@@ -10275,7 +10126,7 @@ class FileUploadComponent {
10275
10126
  <button
10276
10127
  africanies-button
10277
10128
  type="button"
10278
- variant="secondary"
10129
+ variant="flat"
10279
10130
  size="sm"
10280
10131
  [disabled]="disabled()"
10281
10132
  (click)="openFilePicker()"
@@ -10288,7 +10139,7 @@ class FileUploadComponent {
10288
10139
  <button
10289
10140
  africanies-button
10290
10141
  type="button"
10291
- variant="secondary"
10142
+ variant="flat"
10292
10143
  size="sm"
10293
10144
  [disabled]="disabled()"
10294
10145
  (click)="openCamera()"
@@ -10343,7 +10194,7 @@ class FileUploadComponent {
10343
10194
  <button
10344
10195
  africanies-button
10345
10196
  type="button"
10346
- variant="secondary"
10197
+ variant="flat"
10347
10198
  size="sm"
10348
10199
  [disabled]="disabled()"
10349
10200
  (click)="openFilePicker()"
@@ -10412,7 +10263,7 @@ class FileUploadComponent {
10412
10263
  <button
10413
10264
  africanies-button
10414
10265
  type="button"
10415
- variant="secondary"
10266
+ variant="flat"
10416
10267
  size="sm"
10417
10268
  [disabled]="disabled()"
10418
10269
  (click)="openFilePicker(); $event.stopPropagation()"
@@ -10491,7 +10342,7 @@ class FileUploadComponent {
10491
10342
  <span> · </span>
10492
10343
  <button
10493
10344
  type="button"
10494
- class="cursor-pointer bg-transparent p-0 text-caption font-medium text-ink underline underline-offset-2 hover:text-neutral-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-white dark:hover:text-neutral-200"
10345
+ class="cursor-pointer bg-transparent p-0 text-caption font-medium text-ink underline underline-offset-2 hover:text-neutral-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ink dark:text-white dark:hover:text-neutral-200"
10495
10346
  (click)="openPreview(item); $event.stopPropagation()"
10496
10347
  >
10497
10348
  View
@@ -10575,7 +10426,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10575
10426
  <button
10576
10427
  africanies-button
10577
10428
  type="button"
10578
- variant="secondary"
10429
+ variant="flat"
10579
10430
  size="sm"
10580
10431
  [disabled]="disabled()"
10581
10432
  (click)="openFilePicker()"
@@ -10588,7 +10439,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10588
10439
  <button
10589
10440
  africanies-button
10590
10441
  type="button"
10591
- variant="secondary"
10442
+ variant="flat"
10592
10443
  size="sm"
10593
10444
  [disabled]="disabled()"
10594
10445
  (click)="openCamera()"
@@ -10643,7 +10494,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10643
10494
  <button
10644
10495
  africanies-button
10645
10496
  type="button"
10646
- variant="secondary"
10497
+ variant="flat"
10647
10498
  size="sm"
10648
10499
  [disabled]="disabled()"
10649
10500
  (click)="openFilePicker()"
@@ -10712,7 +10563,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10712
10563
  <button
10713
10564
  africanies-button
10714
10565
  type="button"
10715
- variant="secondary"
10566
+ variant="flat"
10716
10567
  size="sm"
10717
10568
  [disabled]="disabled()"
10718
10569
  (click)="openFilePicker(); $event.stopPropagation()"
@@ -10791,7 +10642,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10791
10642
  <span> · </span>
10792
10643
  <button
10793
10644
  type="button"
10794
- class="cursor-pointer bg-transparent p-0 text-caption font-medium text-ink underline underline-offset-2 hover:text-neutral-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-white dark:hover:text-neutral-200"
10645
+ class="cursor-pointer bg-transparent p-0 text-caption font-medium text-ink underline underline-offset-2 hover:text-neutral-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ink dark:text-white dark:hover:text-neutral-200"
10795
10646
  (click)="openPreview(item); $event.stopPropagation()"
10796
10647
  >
10797
10648
  View
@@ -11205,7 +11056,7 @@ class OtpInputComponent {
11205
11056
  cellClass = computed(() => {
11206
11057
  let classes = 'size-10 shrink-0 rounded-md border border-neutral-300 bg-white text-center text-body font-medium text-ink outline-none transition-colors ' +
11207
11058
  'dark:border-white/25 dark:bg-ink-950 dark:text-white ' +
11208
- 'focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-ink disabled:cursor-not-allowed';
11059
+ `${FORM_FOCUS_CLASS} disabled:cursor-not-allowed`;
11209
11060
  if (this.masked()) {
11210
11061
  classes += ' africanies-otp-cell-masked text-body-lg';
11211
11062
  }
@@ -11614,6 +11465,229 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
11614
11465
  `, styles: [".africanies-otp-cell-masked{-webkit-text-security:disc;text-security:disc}\n"] }]
11615
11466
  }], ctorParameters: () => [], propDecorators: { cellInputs: [{ type: i0.ViewChildren, args: ['cellInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showResend: [{ type: i0.Input, args: [{ isSignal: true, alias: "showResend", required: false }] }], resendPrompt: [{ type: i0.Input, args: [{ isSignal: true, alias: "resendPrompt", required: false }] }], resendLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "resendLabel", required: false }] }], resendCooldown: [{ type: i0.Input, args: [{ isSignal: true, alias: "resendCooldown", required: false }] }], completed: [{ type: i0.Output, args: ["completed"] }], resend: [{ type: i0.Output, args: ["resend"] }] } });
11616
11467
 
11468
+ let nextRadioId = 0;
11469
+ /**
11470
+ * Radio group following the shared AFRICANIES form field pattern.
11471
+ *
11472
+ * **Prefix/suffix slots are omitted** — radios are mutually exclusive options
11473
+ * with labels; affix projection does not apply.
11474
+ *
11475
+ * Selected ring and center disc follow {@link ModeColorService}
11476
+ * (SFN green / STN orange).
11477
+ *
11478
+ * @typeParam T - Option value type.
11479
+ *
11480
+ * @example
11481
+ * ```html
11482
+ * <africanies-radio
11483
+ * label="Shipment mode"
11484
+ * [options]="modeOptions"
11485
+ * [(value)]="mode"
11486
+ * />
11487
+ * ```
11488
+ */
11489
+ class RadioComponent {
11490
+ modeColor = inject(ModeColorService);
11491
+ groupName = `africanies-radio-${++nextRadioId}`;
11492
+ hintId = `${this.groupName}-hint`;
11493
+ errorId = `${this.groupName}-error`;
11494
+ labelClass = FORM_LABEL_CLASS;
11495
+ hintClass = FORM_HINT_CLASS;
11496
+ errorClass = FORM_ERROR_CLASS;
11497
+ /** Group legend / label. */
11498
+ label = input('', /* @ts-ignore */
11499
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
11500
+ /** Helper copy. Hidden while {@link error} is set. */
11501
+ hint = input(undefined, /* @ts-ignore */
11502
+ ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
11503
+ /** Field-level validation message. Distinct from `ErrorStateComponent`. */
11504
+ error = input(null, /* @ts-ignore */
11505
+ ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
11506
+ /** Options rendered as radios. */
11507
+ options = input([], /* @ts-ignore */
11508
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
11509
+ /** Selected option value (`[(value)]` + CVA). */
11510
+ value = model(null, /* @ts-ignore */
11511
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
11512
+ /** Host disable flag (in addition to CVA). */
11513
+ disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled',
11514
+ transform: booleanAttribute });
11515
+ cvaDisabled = signal(false, /* @ts-ignore */
11516
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
11517
+ disabled = computed(() => this.disabledInput() || this.cvaDisabled(), /* @ts-ignore */
11518
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
11519
+ describedBy = computed(() => {
11520
+ if (this.error()) {
11521
+ return this.errorId;
11522
+ }
11523
+ if (this.hint()) {
11524
+ return this.hintId;
11525
+ }
11526
+ return null;
11527
+ }, /* @ts-ignore */
11528
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
11529
+ onChange = () => undefined;
11530
+ onTouched = () => undefined;
11531
+ trackOption(opt) {
11532
+ return String(opt.value);
11533
+ }
11534
+ isSelected(opt) {
11535
+ return Object.is(this.value(), opt.value);
11536
+ }
11537
+ markClass(opt) {
11538
+ const base = 'pointer-events-none flex size-4 items-center justify-center rounded-full border-2 bg-transparent transition-colors ' +
11539
+ FORM_FOCUS_VISIBLE_PEER_CLASS;
11540
+ const err = this.error() ? ' !border-danger dark:!border-danger' : '';
11541
+ if (this.isSelected(opt)) {
11542
+ // Ring uses mode accent; fill stays transparent so the center dot reads clearly.
11543
+ return `${base} ${this.modeColor.classes().border}${err}`;
11544
+ }
11545
+ return `${base} border-neutral-400 dark:border-white${err}`;
11546
+ }
11547
+ /** Selected inner disc — SFN green / STN orange. */
11548
+ dotClass = computed(() => `block size-1.5 rounded-full ${this.modeColor.classes().bg}`, /* @ts-ignore */
11549
+ ...(ngDevMode ? [{ debugName: "dotClass" }] : /* istanbul ignore next */ []));
11550
+ /** @param value - Selected option value or `null`. */
11551
+ writeValue(value) {
11552
+ this.value.set(value);
11553
+ }
11554
+ /** @param fn - Selection change callback. */
11555
+ registerOnChange(fn) {
11556
+ this.onChange = fn;
11557
+ }
11558
+ /** @param fn - Blur callback. */
11559
+ registerOnTouched(fn) {
11560
+ this.onTouched = fn;
11561
+ }
11562
+ /** @param isDisabled - Blocks interaction when true. */
11563
+ setDisabledState(isDisabled) {
11564
+ this.cvaDisabled.set(isDisabled);
11565
+ }
11566
+ select(opt) {
11567
+ if (this.disabled() || opt.disabled) {
11568
+ return;
11569
+ }
11570
+ this.value.set(opt.value);
11571
+ this.onChange(opt.value);
11572
+ }
11573
+ onBlur() {
11574
+ this.onTouched();
11575
+ }
11576
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RadioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11577
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: RadioComponent, isStandalone: true, selector: "africanies-radio", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { classAttribute: "inline-flex max-w-full" }, providers: [
11578
+ {
11579
+ provide: NG_VALUE_ACCESSOR,
11580
+ useExisting: forwardRef(() => RadioComponent),
11581
+ multi: true,
11582
+ },
11583
+ ], ngImport: i0, template: `
11584
+ <fieldset
11585
+ class="m-0 p-0 border-0 min-w-0"
11586
+ [attr.aria-invalid]="error() ? true : null"
11587
+ [attr.aria-describedby]="describedBy()"
11588
+ >
11589
+ <legend [class]="labelClass">{{ label() }}</legend>
11590
+ <div class="flex flex-row flex-wrap gap-x-4 gap-y-2" role="radiogroup">
11591
+ @for (opt of options(); track trackOption(opt)) {
11592
+ <label
11593
+ class="inline-flex items-center gap-2.5 text-body text-ink dark:text-white"
11594
+ [class.opacity-50]="disabled() || opt.disabled"
11595
+ [class.cursor-pointer]="!(disabled() || opt.disabled)"
11596
+ [class.cursor-not-allowed]="disabled() || opt.disabled"
11597
+ >
11598
+ <span class="relative inline-flex size-4 shrink-0">
11599
+ <input
11600
+ type="radio"
11601
+ class="peer absolute inset-0 z-10 m-0 size-full opacity-0"
11602
+ [class.cursor-pointer]="!(disabled() || opt.disabled)"
11603
+ [class.cursor-not-allowed]="disabled() || opt.disabled"
11604
+ [name]="groupName"
11605
+ [checked]="isSelected(opt)"
11606
+ [disabled]="disabled() || !!opt.disabled"
11607
+ (change)="select(opt)"
11608
+ (blur)="onBlur()"
11609
+ />
11610
+ <span [class]="markClass(opt)" aria-hidden="true">
11611
+ @if (isSelected(opt)) {
11612
+ <span [class]="dotClass()"></span>
11613
+ }
11614
+ </span>
11615
+ </span>
11616
+ <span>{{ opt.label }}</span>
11617
+ </label>
11618
+ }
11619
+ </div>
11620
+ @if (error(); as err) {
11621
+ <p [id]="errorId" [class]="errorClass" role="alert">{{ err }}</p>
11622
+ } @else if (hint(); as h) {
11623
+ <p [id]="hintId" [class]="hintClass">{{ h }}</p>
11624
+ }
11625
+ </fieldset>
11626
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
11627
+ }
11628
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RadioComponent, decorators: [{
11629
+ type: Component,
11630
+ args: [{
11631
+ selector: 'africanies-radio',
11632
+ standalone: true,
11633
+ changeDetection: ChangeDetectionStrategy.OnPush,
11634
+ host: {
11635
+ class: 'inline-flex max-w-full',
11636
+ },
11637
+ providers: [
11638
+ {
11639
+ provide: NG_VALUE_ACCESSOR,
11640
+ useExisting: forwardRef(() => RadioComponent),
11641
+ multi: true,
11642
+ },
11643
+ ],
11644
+ template: `
11645
+ <fieldset
11646
+ class="m-0 p-0 border-0 min-w-0"
11647
+ [attr.aria-invalid]="error() ? true : null"
11648
+ [attr.aria-describedby]="describedBy()"
11649
+ >
11650
+ <legend [class]="labelClass">{{ label() }}</legend>
11651
+ <div class="flex flex-row flex-wrap gap-x-4 gap-y-2" role="radiogroup">
11652
+ @for (opt of options(); track trackOption(opt)) {
11653
+ <label
11654
+ class="inline-flex items-center gap-2.5 text-body text-ink dark:text-white"
11655
+ [class.opacity-50]="disabled() || opt.disabled"
11656
+ [class.cursor-pointer]="!(disabled() || opt.disabled)"
11657
+ [class.cursor-not-allowed]="disabled() || opt.disabled"
11658
+ >
11659
+ <span class="relative inline-flex size-4 shrink-0">
11660
+ <input
11661
+ type="radio"
11662
+ class="peer absolute inset-0 z-10 m-0 size-full opacity-0"
11663
+ [class.cursor-pointer]="!(disabled() || opt.disabled)"
11664
+ [class.cursor-not-allowed]="disabled() || opt.disabled"
11665
+ [name]="groupName"
11666
+ [checked]="isSelected(opt)"
11667
+ [disabled]="disabled() || !!opt.disabled"
11668
+ (change)="select(opt)"
11669
+ (blur)="onBlur()"
11670
+ />
11671
+ <span [class]="markClass(opt)" aria-hidden="true">
11672
+ @if (isSelected(opt)) {
11673
+ <span [class]="dotClass()"></span>
11674
+ }
11675
+ </span>
11676
+ </span>
11677
+ <span>{{ opt.label }}</span>
11678
+ </label>
11679
+ }
11680
+ </div>
11681
+ @if (error(); as err) {
11682
+ <p [id]="errorId" [class]="errorClass" role="alert">{{ err }}</p>
11683
+ } @else if (hint(); as h) {
11684
+ <p [id]="hintId" [class]="hintClass">{{ h }}</p>
11685
+ }
11686
+ </fieldset>
11687
+ `,
11688
+ }]
11689
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
11690
+
11617
11691
  /**
11618
11692
  * Radio form control exports.
11619
11693
  */
@@ -12600,7 +12674,7 @@ class ToggleComponent {
12600
12674
  [id]="controlId"
12601
12675
  type="button"
12602
12676
  role="switch"
12603
- class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-border p-0.5 box-border transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:border-white/15"
12677
+ class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-border p-0.5 box-border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ink dark:border-white/15"
12604
12678
  [ngClass]="trackClass()"
12605
12679
  [attr.aria-checked]="on()"
12606
12680
  [attr.aria-busy]="loading() ? true : null"
@@ -12662,7 +12736,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
12662
12736
  [id]="controlId"
12663
12737
  type="button"
12664
12738
  role="switch"
12665
- class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-border p-0.5 box-border transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:border-white/15"
12739
+ class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-border p-0.5 box-border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ink dark:border-white/15"
12666
12740
  [ngClass]="trackClass()"
12667
12741
  [attr.aria-checked]="on()"
12668
12742
  [attr.aria-busy]="loading() ? true : null"
@@ -13498,16 +13572,16 @@ class ShippingModeSwitchComponent {
13498
13572
  const selected = this.mode() === option;
13499
13573
  const compact = this.collapsed();
13500
13574
  const base = 'flex w-full cursor-pointer flex-col items-center justify-center gap-1 rounded-lg border text-center text-caption font-medium transition-colors ' +
13501
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink ' +
13575
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus ' +
13502
13576
  (compact ? 'min-h-9 px-1 py-1.5' : 'min-h-[4.5rem] px-1.5 py-2');
13503
- const idle = 'border-border bg-white text-ink hover:bg-background-welcome dark:border-white/15 dark:bg-ink-950 dark:text-white dark:hover:bg-white/10';
13577
+ const idle = 'border-control bg-surface text-ink hover:bg-background-hover dark:border-white/15 dark:bg-ink-950 dark:text-white dark:hover:bg-white/10';
13504
13578
  if (option === 'stn') {
13505
13579
  return selected
13506
- ? `${base} border-import bg-import text-white`
13580
+ ? `${base} border-import-strong bg-import-strong text-white dark:border-import dark:bg-import`
13507
13581
  : `${base} ${idle}`;
13508
13582
  }
13509
13583
  return selected
13510
- ? `${base} border-export bg-export text-white`
13584
+ ? `${base} border-export-strong bg-export-strong text-white dark:border-export dark:bg-export`
13511
13585
  : `${base} ${idle}`;
13512
13586
  }
13513
13587
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ShippingModeSwitchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -13786,14 +13860,16 @@ const DEFAULT_LINK_ACTIVE$1 = {
13786
13860
  /**
13787
13861
  * App shell side navigation with icons, nested items, and a collapsible rail.
13788
13862
  *
13789
- * **Design - ink spine:** a continuous mode-accent edge with soft active
13790
- * highlights. Collapsed mode keeps icons; hover opens a floating label blade
13791
- * (name + children) instead of a plain tooltip.
13863
+ * **Design - ink spine:** a continuous mode-accent edge; active leaves use
13864
+ * primary fill + white text (portal parity); expanded parents of an active
13865
+ * child use primary text on white. Collapsed mode keeps icons; hover opens a
13866
+ * floating label blade (name + children) instead of a plain tooltip.
13792
13867
  *
13793
13868
  * ## Parent items with children
13794
13869
  * **Expanded rail:** branches start open by default (`expandBranchesByDefault`).
13795
13870
  * Clicking a parent only opens/closes the branch (no route change). When any
13796
- * branch is open, a collapse-all control sits above the list (top right).
13871
+ * branch is open, a sticky collapse-all control sits above the list (top right).
13872
+ * The rail expand/collapse control stays pinned in the logo header.
13797
13873
  * **Collapsed rail:** clicking a parent expands the rail, opens the
13798
13874
  * branch, and activates the first enabled child (router or `activeId`).
13799
13875
  *
@@ -13906,6 +13982,10 @@ class SideNavComponent {
13906
13982
  collapseAllBranches() {
13907
13983
  this.openBranches.set(new Set());
13908
13984
  }
13985
+ /** Open every parent branch (e.g. so the collapse-all control is visible). */
13986
+ expandAllBranches() {
13987
+ this.openBranches.set(new Set(this.collectParentIds(this.items())));
13988
+ }
13909
13989
  onItemEnter(id) {
13910
13990
  if (this.collapsed()) {
13911
13991
  this.hoverId.set(id);
@@ -13938,28 +14018,31 @@ class SideNavComponent {
13938
14018
  }
13939
14019
  rowClass(item) {
13940
14020
  const base = 'group relative flex w-full items-center gap-2 rounded-lg text-body-sm no-underline transition-colors ' +
13941
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink ' +
14021
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus ' +
13942
14022
  (this.collapsed() ? 'justify-center px-1.5 py-2' : 'px-3 py-2') +
13943
14023
  (item.disabled ? ' cursor-not-allowed opacity-50' : ' cursor-pointer');
13944
14024
  const leaf = this.isLeafActive(item);
13945
14025
  const ancestor = !leaf && this.isItemActive(item);
14026
+ const colors = this.modeColor.classes();
14027
+ // Portal: active leaf / collapsed rail = primary fill + white text.
13946
14028
  if (leaf) {
13947
- return `${base} ${this.modeColor.classes().soft} ${this.modeColor.classes().text} font-semibold`;
14029
+ return `${base} ${colors.activeFill} font-semibold`;
13948
14030
  }
14031
+ // Portal: expanded parent of active child = primary text on white.
13949
14032
  if (ancestor) {
13950
- return `${base} ${this.modeColor.classes().soft} ${this.modeColor.classes().text} font-semibold`;
14033
+ return `${base} bg-transparent ${colors.text} font-semibold`;
13951
14034
  }
13952
- return (`${base} font-medium text-neutral-600 hover:bg-background-welcome hover:text-ink ` +
14035
+ return (`${base} font-medium text-neutral-600 hover:bg-background-hover hover:text-ink ` +
13953
14036
  'dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white');
13954
14037
  }
13955
14038
  childRowClass(item) {
13956
14039
  const base = 'relative flex w-full items-center gap-2 rounded-lg px-3 py-1.5 text-body-sm no-underline transition-colors ' +
13957
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink ' +
14040
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus ' +
13958
14041
  (item.disabled ? 'cursor-not-allowed opacity-50 ' : 'cursor-pointer ');
13959
14042
  if (this.isLeafActive(item)) {
13960
- return `${base} ${this.modeColor.classes().soft} ${this.modeColor.classes().text} font-semibold`;
14043
+ return `${base} ${this.modeColor.classes().activeFill} font-semibold`;
13961
14044
  }
13962
- return (`${base} font-medium text-neutral-600 hover:bg-background-welcome hover:text-ink ` +
14045
+ return (`${base} font-medium text-neutral-600 hover:bg-background-hover hover:text-ink ` +
13963
14046
  'dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white');
13964
14047
  }
13965
14048
  /**
@@ -14102,13 +14185,13 @@ class SideNavComponent {
14102
14185
  return null;
14103
14186
  }
14104
14187
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SideNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14105
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SideNavComponent, isStandalone: true, selector: "africanies-side-nav", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, activeId: { classPropertyName: "activeId", publicName: "activeId", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, expandParents: { classPropertyName: "expandParents", publicName: "expandParents", isSignal: true, isRequired: false, transformFunction: null }, expandBranchesByDefault: { classPropertyName: "expandBranchesByDefault", publicName: "expandBranchesByDefault", isSignal: true, isRequired: false, transformFunction: null }, linkActiveOptions: { classPropertyName: "linkActiveOptions", publicName: "linkActiveOptions", isSignal: true, isRequired: false, transformFunction: null }, showLogo: { classPropertyName: "showLogo", publicName: "showLogo", isSignal: true, isRequired: false, transformFunction: null }, logoSrc: { classPropertyName: "logoSrc", publicName: "logoSrc", isSignal: true, isRequired: false, transformFunction: null }, logoMiniSrc: { classPropertyName: "logoMiniSrc", publicName: "logoMiniSrc", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange", activeId: "activeIdChange" }, host: { classAttribute: "relative z-20 block h-full" }, ngImport: i0, template: " <aside\n class=\"relative flex h-full flex-col overflow-visible border-r border-border bg-white transition-[width] duration-200 ease-out dark:border-white/10 dark:bg-ink\"\n [style.width]=\"collapsed() ? '3.75rem' : '15rem'\"\n [attr.aria-label]=\"ariaLabel()\"\n >\n <div\n class=\"pointer-events-none absolute inset-y-0 left-0 w-[3px] transition-colors\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n\n <div\n class=\"flex shrink-0 items-center gap-2 border-b border-border py-3 dark:border-white/10\"\n [class.flex-col]=\"collapsed()\"\n [class.px-3]=\"collapsed()\"\n [class.pl-4]=\"!collapsed()\"\n [class.pr-2]=\"!collapsed()\"\n >\n @if (collapsed()) {\n @if (showLogo()) {\n <img\n [src]=\"resolvedMiniLogo()\"\n alt=\"African Import Export Solutions\"\n class=\"size-8 shrink-0 object-contain\"\n />\n }\n } @else {\n @if (showLogo()) {\n <div class=\"min-w-0 flex-1\">\n <img\n [src]=\"resolvedFullLogo()\"\n alt=\"African Import Export Solutions\"\n class=\"h-9 max-w-full object-contain object-left\"\n />\n </div>\n } @else {\n <div\n class=\"min-w-0 flex-1 truncate text-body-sm font-semibold text-ink dark:text-white\"\n >\n <ng-content select=\"[brand]\" />\n </div>\n }\n }\n <button\n type=\"button\"\n class=\"inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-background-welcome hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white\"\n [attr.aria-label]=\"\n collapsed() ? 'Expand navigation' : 'Collapse navigation'\n \"\n [attr.aria-expanded]=\"!collapsed()\"\n (click)=\"toggleCollapsed()\"\n >\n <africanies-icon\n [name]=\"collapsed() ? 'angle-double-right' : 'angle-double-left'\"\n [size]=\"16\"\n />\n </button>\n </div>\n\n <nav class=\"africanies-overlay-scroll relative flex-1 overflow-y-auto overflow-x-visible px-3 py-3\">\n @if (!collapsed() && expandParents() && hasOpenBranches()) {\n <button\n type=\"button\"\n class=\"absolute right-1.5 top-1.5 z-10 inline-flex size-7 cursor-pointer items-center justify-center rounded-md bg-white text-neutral-500 shadow-sm ring-1 ring-border/80 transition-colors hover:bg-background-welcome hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:bg-ink dark:text-neutral-400 dark:ring-white/15 dark:hover:bg-white/10 dark:hover:text-white\"\n aria-label=\"Collapse all\"\n (click)=\"collapseAllBranches()\"\n >\n <africanies-icon name=\"compress\" [size]=\"14\" />\n </button>\n }\n <ul class=\"m-0 flex list-none flex-col gap-0.5 p-0\">\n @for (item of items(); track item.id) {\n <li\n class=\"relative overflow-visible\"\n (mouseenter)=\"onItemEnter(item.id)\"\n (mouseleave)=\"onItemLeave(item.id)\"\n >\n @if (item.routerLink !== null && item.routerLink !== undefined) {\n <a\n [routerLink]=\"item.disabled ? null : item.routerLink\"\n [queryParams]=\"item.queryParams\"\n [fragment]=\"item.fragment\"\n [class]=\"rowClass(item)\"\n [attr.aria-current]=\"isLeafActive(item) ? 'page' : null\"\n [attr.aria-expanded]=\"\n item.children?.length ? isBranchOpen(item.id) : null\n \"\n [attr.aria-disabled]=\"item.disabled ? true : null\"\n (click)=\"onActivate(item, $event)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowInner\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n [class]=\"rowClass(item)\"\n [disabled]=\"!!item.disabled\"\n [attr.aria-current]=\"isLeafActive(item) ? 'page' : null\"\n [attr.aria-expanded]=\"\n item.children?.length ? isBranchOpen(item.id) : null\n \"\n (click)=\"onActivate(item, $event)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowInner\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n </button>\n }\n\n @if (!collapsed() && item.children?.length) {\n <div\n class=\"grid transition-[grid-template-rows] duration-200 ease-out\"\n [style.grid-template-rows]=\"\n isBranchOpen(item.id) ? '1fr' : '0fr'\n \"\n [attr.aria-hidden]=\"!isBranchOpen(item.id)\"\n [class.pointer-events-none]=\"!isBranchOpen(item.id)\"\n >\n <ul\n class=\"relative m-0 mt-0.5 min-h-0 list-none space-y-0.5 overflow-hidden py-1 pl-4 before:absolute before:bottom-1 before:left-[1.35rem] before:top-0 before:w-px before:bg-border dark:before:bg-white/15\"\n >\n @for (child of item.children; track child.id) {\n <li\n class=\"transition-opacity duration-200 ease-out\"\n [class.opacity-0]=\"!isBranchOpen(item.id)\"\n [class.opacity-100]=\"isBranchOpen(item.id)\"\n >\n @if (\n child.routerLink !== null &&\n child.routerLink !== undefined\n ) {\n <a\n [routerLink]=\"child.disabled ? null : child.routerLink\"\n [queryParams]=\"child.queryParams\"\n [fragment]=\"child.fragment\"\n [class]=\"childRowClass(child)\"\n [attr.aria-current]=\"\n isLeafActive(child) ? 'page' : null\n \"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon\n [name]=\"icon\"\n [size]=\"14\"\n class=\"shrink-0 opacity-80\"\n />\n }\n <span class=\"truncate\">{{ child.label }}</span>\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: false,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n [class]=\"childRowClass(child)\"\n [disabled]=\"!!child.disabled\"\n [attr.aria-current]=\"\n isLeafActive(child) ? 'page' : null\n \"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon\n [name]=\"icon\"\n [size]=\"14\"\n class=\"shrink-0 opacity-80\"\n />\n }\n <span class=\"truncate\">{{ child.label }}</span>\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: false,\n }\"\n />\n </button>\n }\n </li>\n }\n </ul>\n </div>\n }\n\n @if (collapsed() && hoverId() === item.id) {\n <div\n class=\"absolute left-full top-0 z-50 ml-2 min-w-[11rem] overflow-hidden rounded-md border border-border bg-ink text-white shadow-lg dark:border-white/15\"\n role=\"tooltip\"\n >\n <div\n class=\"absolute inset-y-0 left-0 w-1\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n <div class=\"border-b border-white/10 px-3 py-2 pl-4\">\n <p class=\"m-0 text-body-sm font-semibold\">{{ item.label }}</p>\n </div>\n @if (item.children?.length) {\n <ul class=\"m-0 list-none space-y-0.5 p-1.5\">\n @for (child of item.children; track child.id) {\n <li>\n @if (\n child.routerLink !== null &&\n child.routerLink !== undefined\n ) {\n <a\n [routerLink]=\"\n child.disabled ? null : child.routerLink\n \"\n [queryParams]=\"child.queryParams\"\n [fragment]=\"child.fragment\"\n class=\"relative flex items-center gap-2 rounded px-2.5 py-1.5 text-body-sm text-white/85 no-underline transition-colors hover:bg-white/10 hover:text-white\"\n [class.opacity-50]=\"child.disabled\"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"14\" />\n }\n {{ child.label }}\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: true,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"relative flex w-full items-center gap-2 rounded px-2.5 py-1.5 text-left text-body-sm text-white/85 transition-colors hover:bg-white/10 hover:text-white\"\n [disabled]=\"!!child.disabled\"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"14\" />\n }\n {{ child.label }}\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: true,\n }\"\n />\n </button>\n }\n </li>\n }\n </ul>\n }\n </div>\n }\n </li>\n }\n </ul>\n </nav>\n\n <ng-content select=\"[footer]\" />\n </aside>\n\n <ng-template #rowInner let-item>\n <span class=\"inline-flex size-5 shrink-0 items-center justify-center\">\n @if (item.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"16\" />\n } @else {\n <span\n class=\"size-1.5 rounded-full bg-current opacity-70\"\n aria-hidden=\"true\"\n ></span>\n }\n </span>\n @if (!collapsed()) {\n <span class=\"min-w-0 flex-1 truncate text-left\">{{ item.label }}</span>\n @if (item.children?.length) {\n <africanies-icon\n name=\"chevron-down\"\n [size]=\"14\"\n class=\"shrink-0 opacity-70 transition-transform duration-200 ease-out\"\n [class.rotate-180]=\"isBranchOpen(item.id)\"\n />\n }\n }\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: item.badge,\n compact: collapsed(),\n }\"\n />\n </ng-template>\n\n <ng-template #navBadge let-visible=\"visible\" let-compact=\"compact\">\n @if (visible) {\n <span\n class=\"pointer-events-none absolute z-10 overflow-visible\"\n [class]=\"compact ? 'right-0.5 top-0.5 h-2 w-2' : 'right-1.5 top-1 h-2 w-2'\"\n aria-hidden=\"true\"\n >\n <span\n class=\"africanies-side-nav-badge-ping absolute left-1/2 top-1/2 size-2 rounded-full bg-danger\"\n ></span>\n <span\n class=\"absolute left-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-danger ring-2 ring-white dark:ring-ink\"\n ></span>\n </span>\n }\n </ng-template>", styles: ["@keyframes africanies-side-nav-badge-pulse{0%{transform:translate(-50%,-50%) scale(.85);opacity:.9}70%{transform:translate(-50%,-50%) scale(2.4);opacity:0}to{transform:translate(-50%,-50%) scale(2.4);opacity:0}}.africanies-side-nav-badge-ping{animation:africanies-side-nav-badge-pulse 1.4s cubic-bezier(0,0,.2,1) infinite}@media(prefers-reduced-motion:reduce){.africanies-side-nav-badge-ping{animation:none;opacity:.5}}\n"], dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14188
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SideNavComponent, isStandalone: true, selector: "africanies-side-nav", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, activeId: { classPropertyName: "activeId", publicName: "activeId", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, expandParents: { classPropertyName: "expandParents", publicName: "expandParents", isSignal: true, isRequired: false, transformFunction: null }, expandBranchesByDefault: { classPropertyName: "expandBranchesByDefault", publicName: "expandBranchesByDefault", isSignal: true, isRequired: false, transformFunction: null }, linkActiveOptions: { classPropertyName: "linkActiveOptions", publicName: "linkActiveOptions", isSignal: true, isRequired: false, transformFunction: null }, showLogo: { classPropertyName: "showLogo", publicName: "showLogo", isSignal: true, isRequired: false, transformFunction: null }, logoSrc: { classPropertyName: "logoSrc", publicName: "logoSrc", isSignal: true, isRequired: false, transformFunction: null }, logoMiniSrc: { classPropertyName: "logoMiniSrc", publicName: "logoMiniSrc", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange", activeId: "activeIdChange" }, host: { classAttribute: "relative z-20 block h-full min-h-0" }, ngImport: i0, template: " <aside\n class=\"relative flex h-full min-h-0 flex-col overflow-visible border-r border-border bg-surface transition-[width] duration-200 ease-out dark:border-white/10 dark:bg-ink-950\"\n [style.width]=\"collapsed() ? '3.75rem' : '15rem'\"\n [attr.aria-label]=\"ariaLabel()\"\n >\n <div\n class=\"pointer-events-none absolute inset-y-0 left-0 w-[3px] transition-colors\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n\n <div\n class=\"sticky top-0 z-20 flex shrink-0 items-center gap-2 border-b border-border bg-surface py-3 dark:border-white/10 dark:bg-ink-950\"\n [class.flex-col]=\"collapsed()\"\n [class.px-3]=\"collapsed()\"\n [class.pl-4]=\"!collapsed()\"\n [class.pr-2]=\"!collapsed()\"\n >\n @if (collapsed()) {\n @if (showLogo()) {\n <img\n [src]=\"resolvedMiniLogo()\"\n alt=\"African Import Export Solutions\"\n class=\"size-8 shrink-0 object-contain\"\n />\n }\n } @else {\n @if (showLogo()) {\n <div class=\"min-w-0 flex-1\">\n <img\n [src]=\"resolvedFullLogo()\"\n alt=\"African Import Export Solutions\"\n class=\"h-9 max-w-full object-contain object-left\"\n />\n </div>\n } @else {\n <div\n class=\"min-w-0 flex-1 truncate text-body-sm font-semibold text-ink dark:text-white\"\n >\n <ng-content select=\"[brand]\" />\n </div>\n }\n }\n <button\n type=\"button\"\n class=\"inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white\"\n data-onboarding=\"nav-collapse\"\n [attr.aria-label]=\"\n collapsed() ? 'Expand navigation' : 'Collapse navigation'\n \"\n [attr.aria-expanded]=\"!collapsed()\"\n (click)=\"toggleCollapsed()\"\n >\n <africanies-icon\n [name]=\"collapsed() ? 'angle-double-right' : 'angle-double-left'\"\n [size]=\"16\"\n />\n </button>\n </div>\n\n <nav\n class=\"africanies-overlay-scroll relative min-h-0 flex-1 overflow-y-auto overflow-x-visible px-3 py-3\"\n >\n @if (!collapsed() && expandParents() && hasOpenBranches()) {\n <!-- Zero-height sticky host: keeps the control pinned without reserving a row. -->\n <div class=\"sticky top-0 z-10 h-0 overflow-visible\">\n <button\n type=\"button\"\n class=\"absolute right-0 top-0 inline-flex size-7 cursor-pointer items-center justify-center rounded-md bg-surface text-neutral-600 shadow-sm ring-1 ring-border-strong transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:bg-ink-950 dark:text-neutral-400 dark:ring-white/15 dark:hover:bg-white/10 dark:hover:text-white\"\n data-onboarding=\"nav-branches-collapse\"\n aria-label=\"Collapse all\"\n (click)=\"collapseAllBranches()\"\n >\n <africanies-icon name=\"compress\" [size]=\"14\" />\n </button>\n </div>\n }\n <ul class=\"m-0 flex list-none flex-col gap-0.5 p-0\">\n @for (item of items(); track item.id) {\n <li\n class=\"relative overflow-visible last:mb-0\"\n [class.mb-3]=\"!collapsed() && !item.children?.length\"\n (mouseenter)=\"onItemEnter(item.id)\"\n (mouseleave)=\"onItemLeave(item.id)\"\n >\n @if (item.routerLink !== null && item.routerLink !== undefined) {\n <a\n [routerLink]=\"item.disabled ? null : item.routerLink\"\n [queryParams]=\"item.queryParams\"\n [fragment]=\"item.fragment\"\n [class]=\"rowClass(item)\"\n [attr.aria-current]=\"isLeafActive(item) ? 'page' : null\"\n [attr.aria-expanded]=\"\n item.children?.length ? isBranchOpen(item.id) : null\n \"\n [attr.aria-disabled]=\"item.disabled ? true : null\"\n (click)=\"onActivate(item, $event)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowInner\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n [class]=\"rowClass(item)\"\n [disabled]=\"!!item.disabled\"\n [attr.aria-current]=\"isLeafActive(item) ? 'page' : null\"\n [attr.aria-expanded]=\"\n item.children?.length ? isBranchOpen(item.id) : null\n \"\n (click)=\"onActivate(item, $event)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowInner\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n </button>\n }\n\n @if (!collapsed() && item.children?.length) {\n <div\n class=\"grid transition-[grid-template-rows] duration-200 ease-out\"\n [style.grid-template-rows]=\"\n isBranchOpen(item.id) ? '1fr' : '0fr'\n \"\n [attr.aria-hidden]=\"!isBranchOpen(item.id)\"\n [class.pointer-events-none]=\"!isBranchOpen(item.id)\"\n >\n <ul\n class=\"relative m-0 mt-0.5 min-h-0 list-none space-y-0.5 overflow-hidden py-1 pl-4 before:absolute before:bottom-1 before:left-[1.35rem] before:top-0 before:w-px before:bg-border dark:before:bg-white/15\"\n >\n @for (child of item.children; track child.id) {\n <li\n class=\"transition-opacity duration-200 ease-out\"\n [class.opacity-0]=\"!isBranchOpen(item.id)\"\n [class.opacity-100]=\"isBranchOpen(item.id)\"\n >\n @if (\n child.routerLink !== null &&\n child.routerLink !== undefined\n ) {\n <a\n [routerLink]=\"child.disabled ? null : child.routerLink\"\n [queryParams]=\"child.queryParams\"\n [fragment]=\"child.fragment\"\n [class]=\"childRowClass(child)\"\n [attr.aria-current]=\"\n isLeafActive(child) ? 'page' : null\n \"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon\n [name]=\"icon\"\n [size]=\"14\"\n class=\"shrink-0 opacity-80\"\n />\n }\n <span class=\"truncate\">{{ child.label }}</span>\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: false,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n [class]=\"childRowClass(child)\"\n [disabled]=\"!!child.disabled\"\n [attr.aria-current]=\"\n isLeafActive(child) ? 'page' : null\n \"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon\n [name]=\"icon\"\n [size]=\"14\"\n class=\"shrink-0 opacity-80\"\n />\n }\n <span class=\"truncate\">{{ child.label }}</span>\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: false,\n }\"\n />\n </button>\n }\n </li>\n }\n </ul>\n </div>\n }\n\n @if (collapsed() && hoverId() === item.id) {\n <div\n class=\"absolute left-full top-0 z-50 ml-2 min-w-[11rem] overflow-hidden rounded-md border border-border bg-ink text-white shadow-lg dark:border-white/15\"\n role=\"tooltip\"\n >\n <div\n class=\"absolute inset-y-0 left-0 w-1\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n <div class=\"border-b border-white/10 px-3 py-2 pl-4\">\n <p class=\"m-0 text-body-sm font-semibold\">{{ item.label }}</p>\n </div>\n @if (item.children?.length) {\n <ul class=\"m-0 list-none space-y-0.5 p-1.5\">\n @for (child of item.children; track child.id) {\n <li>\n @if (\n child.routerLink !== null &&\n child.routerLink !== undefined\n ) {\n <a\n [routerLink]=\"\n child.disabled ? null : child.routerLink\n \"\n [queryParams]=\"child.queryParams\"\n [fragment]=\"child.fragment\"\n class=\"relative flex items-center gap-2 rounded px-2.5 py-1.5 text-body-sm text-white/85 no-underline transition-colors hover:bg-white/10 hover:text-white\"\n [class.opacity-50]=\"child.disabled\"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"14\" />\n }\n {{ child.label }}\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: true,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"relative flex w-full items-center gap-2 rounded px-2.5 py-1.5 text-left text-body-sm text-white/85 transition-colors hover:bg-white/10 hover:text-white\"\n [disabled]=\"!!child.disabled\"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"14\" />\n }\n {{ child.label }}\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: true,\n }\"\n />\n </button>\n }\n </li>\n }\n </ul>\n }\n </div>\n }\n </li>\n }\n </ul>\n </nav>\n\n <ng-content select=\"[footer]\" />\n </aside>\n\n <ng-template #rowInner let-item>\n <span class=\"inline-flex size-5 shrink-0 items-center justify-center\">\n @if (item.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"16\" />\n } @else {\n <span\n class=\"size-1.5 rounded-full bg-current opacity-70\"\n aria-hidden=\"true\"\n ></span>\n }\n </span>\n @if (!collapsed()) {\n <span class=\"min-w-0 flex-1 truncate text-left\">{{ item.label }}</span>\n @if (item.children?.length) {\n <africanies-icon\n name=\"chevron-down\"\n [size]=\"14\"\n class=\"shrink-0 opacity-70 transition-transform duration-200 ease-out\"\n [class.rotate-180]=\"isBranchOpen(item.id)\"\n />\n }\n }\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: item.badge,\n compact: collapsed(),\n }\"\n />\n </ng-template>\n\n <ng-template #navBadge let-visible=\"visible\" let-compact=\"compact\">\n @if (visible) {\n <span\n class=\"pointer-events-none absolute z-10 overflow-visible\"\n [class]=\"compact ? 'right-0.5 top-0.5 h-2 w-2' : 'right-1.5 top-1 h-2 w-2'\"\n aria-hidden=\"true\"\n >\n <span\n class=\"africanies-side-nav-badge-ping absolute left-1/2 top-1/2 size-2 rounded-full bg-danger\"\n ></span>\n <span\n class=\"absolute left-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-danger ring-2 ring-white dark:ring-ink\"\n ></span>\n </span>\n }\n </ng-template>", styles: ["@keyframes africanies-side-nav-badge-pulse{0%{transform:translate(-50%,-50%) scale(.85);opacity:.9}70%{transform:translate(-50%,-50%) scale(2.4);opacity:0}to{transform:translate(-50%,-50%) scale(2.4);opacity:0}}.africanies-side-nav-badge-ping{animation:africanies-side-nav-badge-pulse 1.4s cubic-bezier(0,0,.2,1) infinite}@media(prefers-reduced-motion:reduce){.africanies-side-nav-badge-ping{animation:none;opacity:.5}}\n"], dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14106
14189
  }
14107
14190
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SideNavComponent, decorators: [{
14108
14191
  type: Component,
14109
14192
  args: [{ selector: 'africanies-side-nav', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AfricaniesIconComponent, RouterLink, NgTemplateOutlet], host: {
14110
- class: 'relative z-20 block h-full',
14111
- }, template: " <aside\n class=\"relative flex h-full flex-col overflow-visible border-r border-border bg-white transition-[width] duration-200 ease-out dark:border-white/10 dark:bg-ink\"\n [style.width]=\"collapsed() ? '3.75rem' : '15rem'\"\n [attr.aria-label]=\"ariaLabel()\"\n >\n <div\n class=\"pointer-events-none absolute inset-y-0 left-0 w-[3px] transition-colors\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n\n <div\n class=\"flex shrink-0 items-center gap-2 border-b border-border py-3 dark:border-white/10\"\n [class.flex-col]=\"collapsed()\"\n [class.px-3]=\"collapsed()\"\n [class.pl-4]=\"!collapsed()\"\n [class.pr-2]=\"!collapsed()\"\n >\n @if (collapsed()) {\n @if (showLogo()) {\n <img\n [src]=\"resolvedMiniLogo()\"\n alt=\"African Import Export Solutions\"\n class=\"size-8 shrink-0 object-contain\"\n />\n }\n } @else {\n @if (showLogo()) {\n <div class=\"min-w-0 flex-1\">\n <img\n [src]=\"resolvedFullLogo()\"\n alt=\"African Import Export Solutions\"\n class=\"h-9 max-w-full object-contain object-left\"\n />\n </div>\n } @else {\n <div\n class=\"min-w-0 flex-1 truncate text-body-sm font-semibold text-ink dark:text-white\"\n >\n <ng-content select=\"[brand]\" />\n </div>\n }\n }\n <button\n type=\"button\"\n class=\"inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-background-welcome hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white\"\n [attr.aria-label]=\"\n collapsed() ? 'Expand navigation' : 'Collapse navigation'\n \"\n [attr.aria-expanded]=\"!collapsed()\"\n (click)=\"toggleCollapsed()\"\n >\n <africanies-icon\n [name]=\"collapsed() ? 'angle-double-right' : 'angle-double-left'\"\n [size]=\"16\"\n />\n </button>\n </div>\n\n <nav class=\"africanies-overlay-scroll relative flex-1 overflow-y-auto overflow-x-visible px-3 py-3\">\n @if (!collapsed() && expandParents() && hasOpenBranches()) {\n <button\n type=\"button\"\n class=\"absolute right-1.5 top-1.5 z-10 inline-flex size-7 cursor-pointer items-center justify-center rounded-md bg-white text-neutral-500 shadow-sm ring-1 ring-border/80 transition-colors hover:bg-background-welcome hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:bg-ink dark:text-neutral-400 dark:ring-white/15 dark:hover:bg-white/10 dark:hover:text-white\"\n aria-label=\"Collapse all\"\n (click)=\"collapseAllBranches()\"\n >\n <africanies-icon name=\"compress\" [size]=\"14\" />\n </button>\n }\n <ul class=\"m-0 flex list-none flex-col gap-0.5 p-0\">\n @for (item of items(); track item.id) {\n <li\n class=\"relative overflow-visible\"\n (mouseenter)=\"onItemEnter(item.id)\"\n (mouseleave)=\"onItemLeave(item.id)\"\n >\n @if (item.routerLink !== null && item.routerLink !== undefined) {\n <a\n [routerLink]=\"item.disabled ? null : item.routerLink\"\n [queryParams]=\"item.queryParams\"\n [fragment]=\"item.fragment\"\n [class]=\"rowClass(item)\"\n [attr.aria-current]=\"isLeafActive(item) ? 'page' : null\"\n [attr.aria-expanded]=\"\n item.children?.length ? isBranchOpen(item.id) : null\n \"\n [attr.aria-disabled]=\"item.disabled ? true : null\"\n (click)=\"onActivate(item, $event)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowInner\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n [class]=\"rowClass(item)\"\n [disabled]=\"!!item.disabled\"\n [attr.aria-current]=\"isLeafActive(item) ? 'page' : null\"\n [attr.aria-expanded]=\"\n item.children?.length ? isBranchOpen(item.id) : null\n \"\n (click)=\"onActivate(item, $event)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowInner\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n </button>\n }\n\n @if (!collapsed() && item.children?.length) {\n <div\n class=\"grid transition-[grid-template-rows] duration-200 ease-out\"\n [style.grid-template-rows]=\"\n isBranchOpen(item.id) ? '1fr' : '0fr'\n \"\n [attr.aria-hidden]=\"!isBranchOpen(item.id)\"\n [class.pointer-events-none]=\"!isBranchOpen(item.id)\"\n >\n <ul\n class=\"relative m-0 mt-0.5 min-h-0 list-none space-y-0.5 overflow-hidden py-1 pl-4 before:absolute before:bottom-1 before:left-[1.35rem] before:top-0 before:w-px before:bg-border dark:before:bg-white/15\"\n >\n @for (child of item.children; track child.id) {\n <li\n class=\"transition-opacity duration-200 ease-out\"\n [class.opacity-0]=\"!isBranchOpen(item.id)\"\n [class.opacity-100]=\"isBranchOpen(item.id)\"\n >\n @if (\n child.routerLink !== null &&\n child.routerLink !== undefined\n ) {\n <a\n [routerLink]=\"child.disabled ? null : child.routerLink\"\n [queryParams]=\"child.queryParams\"\n [fragment]=\"child.fragment\"\n [class]=\"childRowClass(child)\"\n [attr.aria-current]=\"\n isLeafActive(child) ? 'page' : null\n \"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon\n [name]=\"icon\"\n [size]=\"14\"\n class=\"shrink-0 opacity-80\"\n />\n }\n <span class=\"truncate\">{{ child.label }}</span>\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: false,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n [class]=\"childRowClass(child)\"\n [disabled]=\"!!child.disabled\"\n [attr.aria-current]=\"\n isLeafActive(child) ? 'page' : null\n \"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon\n [name]=\"icon\"\n [size]=\"14\"\n class=\"shrink-0 opacity-80\"\n />\n }\n <span class=\"truncate\">{{ child.label }}</span>\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: false,\n }\"\n />\n </button>\n }\n </li>\n }\n </ul>\n </div>\n }\n\n @if (collapsed() && hoverId() === item.id) {\n <div\n class=\"absolute left-full top-0 z-50 ml-2 min-w-[11rem] overflow-hidden rounded-md border border-border bg-ink text-white shadow-lg dark:border-white/15\"\n role=\"tooltip\"\n >\n <div\n class=\"absolute inset-y-0 left-0 w-1\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n <div class=\"border-b border-white/10 px-3 py-2 pl-4\">\n <p class=\"m-0 text-body-sm font-semibold\">{{ item.label }}</p>\n </div>\n @if (item.children?.length) {\n <ul class=\"m-0 list-none space-y-0.5 p-1.5\">\n @for (child of item.children; track child.id) {\n <li>\n @if (\n child.routerLink !== null &&\n child.routerLink !== undefined\n ) {\n <a\n [routerLink]=\"\n child.disabled ? null : child.routerLink\n \"\n [queryParams]=\"child.queryParams\"\n [fragment]=\"child.fragment\"\n class=\"relative flex items-center gap-2 rounded px-2.5 py-1.5 text-body-sm text-white/85 no-underline transition-colors hover:bg-white/10 hover:text-white\"\n [class.opacity-50]=\"child.disabled\"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"14\" />\n }\n {{ child.label }}\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: true,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"relative flex w-full items-center gap-2 rounded px-2.5 py-1.5 text-left text-body-sm text-white/85 transition-colors hover:bg-white/10 hover:text-white\"\n [disabled]=\"!!child.disabled\"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"14\" />\n }\n {{ child.label }}\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: true,\n }\"\n />\n </button>\n }\n </li>\n }\n </ul>\n }\n </div>\n }\n </li>\n }\n </ul>\n </nav>\n\n <ng-content select=\"[footer]\" />\n </aside>\n\n <ng-template #rowInner let-item>\n <span class=\"inline-flex size-5 shrink-0 items-center justify-center\">\n @if (item.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"16\" />\n } @else {\n <span\n class=\"size-1.5 rounded-full bg-current opacity-70\"\n aria-hidden=\"true\"\n ></span>\n }\n </span>\n @if (!collapsed()) {\n <span class=\"min-w-0 flex-1 truncate text-left\">{{ item.label }}</span>\n @if (item.children?.length) {\n <africanies-icon\n name=\"chevron-down\"\n [size]=\"14\"\n class=\"shrink-0 opacity-70 transition-transform duration-200 ease-out\"\n [class.rotate-180]=\"isBranchOpen(item.id)\"\n />\n }\n }\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: item.badge,\n compact: collapsed(),\n }\"\n />\n </ng-template>\n\n <ng-template #navBadge let-visible=\"visible\" let-compact=\"compact\">\n @if (visible) {\n <span\n class=\"pointer-events-none absolute z-10 overflow-visible\"\n [class]=\"compact ? 'right-0.5 top-0.5 h-2 w-2' : 'right-1.5 top-1 h-2 w-2'\"\n aria-hidden=\"true\"\n >\n <span\n class=\"africanies-side-nav-badge-ping absolute left-1/2 top-1/2 size-2 rounded-full bg-danger\"\n ></span>\n <span\n class=\"absolute left-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-danger ring-2 ring-white dark:ring-ink\"\n ></span>\n </span>\n }\n </ng-template>", styles: ["@keyframes africanies-side-nav-badge-pulse{0%{transform:translate(-50%,-50%) scale(.85);opacity:.9}70%{transform:translate(-50%,-50%) scale(2.4);opacity:0}to{transform:translate(-50%,-50%) scale(2.4);opacity:0}}.africanies-side-nav-badge-ping{animation:africanies-side-nav-badge-pulse 1.4s cubic-bezier(0,0,.2,1) infinite}@media(prefers-reduced-motion:reduce){.africanies-side-nav-badge-ping{animation:none;opacity:.5}}\n"] }]
14193
+ class: 'relative z-20 block h-full min-h-0',
14194
+ }, template: " <aside\n class=\"relative flex h-full min-h-0 flex-col overflow-visible border-r border-border bg-surface transition-[width] duration-200 ease-out dark:border-white/10 dark:bg-ink-950\"\n [style.width]=\"collapsed() ? '3.75rem' : '15rem'\"\n [attr.aria-label]=\"ariaLabel()\"\n >\n <div\n class=\"pointer-events-none absolute inset-y-0 left-0 w-[3px] transition-colors\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n\n <div\n class=\"sticky top-0 z-20 flex shrink-0 items-center gap-2 border-b border-border bg-surface py-3 dark:border-white/10 dark:bg-ink-950\"\n [class.flex-col]=\"collapsed()\"\n [class.px-3]=\"collapsed()\"\n [class.pl-4]=\"!collapsed()\"\n [class.pr-2]=\"!collapsed()\"\n >\n @if (collapsed()) {\n @if (showLogo()) {\n <img\n [src]=\"resolvedMiniLogo()\"\n alt=\"African Import Export Solutions\"\n class=\"size-8 shrink-0 object-contain\"\n />\n }\n } @else {\n @if (showLogo()) {\n <div class=\"min-w-0 flex-1\">\n <img\n [src]=\"resolvedFullLogo()\"\n alt=\"African Import Export Solutions\"\n class=\"h-9 max-w-full object-contain object-left\"\n />\n </div>\n } @else {\n <div\n class=\"min-w-0 flex-1 truncate text-body-sm font-semibold text-ink dark:text-white\"\n >\n <ng-content select=\"[brand]\" />\n </div>\n }\n }\n <button\n type=\"button\"\n class=\"inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white\"\n data-onboarding=\"nav-collapse\"\n [attr.aria-label]=\"\n collapsed() ? 'Expand navigation' : 'Collapse navigation'\n \"\n [attr.aria-expanded]=\"!collapsed()\"\n (click)=\"toggleCollapsed()\"\n >\n <africanies-icon\n [name]=\"collapsed() ? 'angle-double-right' : 'angle-double-left'\"\n [size]=\"16\"\n />\n </button>\n </div>\n\n <nav\n class=\"africanies-overlay-scroll relative min-h-0 flex-1 overflow-y-auto overflow-x-visible px-3 py-3\"\n >\n @if (!collapsed() && expandParents() && hasOpenBranches()) {\n <!-- Zero-height sticky host: keeps the control pinned without reserving a row. -->\n <div class=\"sticky top-0 z-10 h-0 overflow-visible\">\n <button\n type=\"button\"\n class=\"absolute right-0 top-0 inline-flex size-7 cursor-pointer items-center justify-center rounded-md bg-surface text-neutral-600 shadow-sm ring-1 ring-border-strong transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:bg-ink-950 dark:text-neutral-400 dark:ring-white/15 dark:hover:bg-white/10 dark:hover:text-white\"\n data-onboarding=\"nav-branches-collapse\"\n aria-label=\"Collapse all\"\n (click)=\"collapseAllBranches()\"\n >\n <africanies-icon name=\"compress\" [size]=\"14\" />\n </button>\n </div>\n }\n <ul class=\"m-0 flex list-none flex-col gap-0.5 p-0\">\n @for (item of items(); track item.id) {\n <li\n class=\"relative overflow-visible last:mb-0\"\n [class.mb-3]=\"!collapsed() && !item.children?.length\"\n (mouseenter)=\"onItemEnter(item.id)\"\n (mouseleave)=\"onItemLeave(item.id)\"\n >\n @if (item.routerLink !== null && item.routerLink !== undefined) {\n <a\n [routerLink]=\"item.disabled ? null : item.routerLink\"\n [queryParams]=\"item.queryParams\"\n [fragment]=\"item.fragment\"\n [class]=\"rowClass(item)\"\n [attr.aria-current]=\"isLeafActive(item) ? 'page' : null\"\n [attr.aria-expanded]=\"\n item.children?.length ? isBranchOpen(item.id) : null\n \"\n [attr.aria-disabled]=\"item.disabled ? true : null\"\n (click)=\"onActivate(item, $event)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowInner\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n [class]=\"rowClass(item)\"\n [disabled]=\"!!item.disabled\"\n [attr.aria-current]=\"isLeafActive(item) ? 'page' : null\"\n [attr.aria-expanded]=\"\n item.children?.length ? isBranchOpen(item.id) : null\n \"\n (click)=\"onActivate(item, $event)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowInner\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n </button>\n }\n\n @if (!collapsed() && item.children?.length) {\n <div\n class=\"grid transition-[grid-template-rows] duration-200 ease-out\"\n [style.grid-template-rows]=\"\n isBranchOpen(item.id) ? '1fr' : '0fr'\n \"\n [attr.aria-hidden]=\"!isBranchOpen(item.id)\"\n [class.pointer-events-none]=\"!isBranchOpen(item.id)\"\n >\n <ul\n class=\"relative m-0 mt-0.5 min-h-0 list-none space-y-0.5 overflow-hidden py-1 pl-4 before:absolute before:bottom-1 before:left-[1.35rem] before:top-0 before:w-px before:bg-border dark:before:bg-white/15\"\n >\n @for (child of item.children; track child.id) {\n <li\n class=\"transition-opacity duration-200 ease-out\"\n [class.opacity-0]=\"!isBranchOpen(item.id)\"\n [class.opacity-100]=\"isBranchOpen(item.id)\"\n >\n @if (\n child.routerLink !== null &&\n child.routerLink !== undefined\n ) {\n <a\n [routerLink]=\"child.disabled ? null : child.routerLink\"\n [queryParams]=\"child.queryParams\"\n [fragment]=\"child.fragment\"\n [class]=\"childRowClass(child)\"\n [attr.aria-current]=\"\n isLeafActive(child) ? 'page' : null\n \"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon\n [name]=\"icon\"\n [size]=\"14\"\n class=\"shrink-0 opacity-80\"\n />\n }\n <span class=\"truncate\">{{ child.label }}</span>\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: false,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n [class]=\"childRowClass(child)\"\n [disabled]=\"!!child.disabled\"\n [attr.aria-current]=\"\n isLeafActive(child) ? 'page' : null\n \"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon\n [name]=\"icon\"\n [size]=\"14\"\n class=\"shrink-0 opacity-80\"\n />\n }\n <span class=\"truncate\">{{ child.label }}</span>\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: false,\n }\"\n />\n </button>\n }\n </li>\n }\n </ul>\n </div>\n }\n\n @if (collapsed() && hoverId() === item.id) {\n <div\n class=\"absolute left-full top-0 z-50 ml-2 min-w-[11rem] overflow-hidden rounded-md border border-border bg-ink text-white shadow-lg dark:border-white/15\"\n role=\"tooltip\"\n >\n <div\n class=\"absolute inset-y-0 left-0 w-1\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n <div class=\"border-b border-white/10 px-3 py-2 pl-4\">\n <p class=\"m-0 text-body-sm font-semibold\">{{ item.label }}</p>\n </div>\n @if (item.children?.length) {\n <ul class=\"m-0 list-none space-y-0.5 p-1.5\">\n @for (child of item.children; track child.id) {\n <li>\n @if (\n child.routerLink !== null &&\n child.routerLink !== undefined\n ) {\n <a\n [routerLink]=\"\n child.disabled ? null : child.routerLink\n \"\n [queryParams]=\"child.queryParams\"\n [fragment]=\"child.fragment\"\n class=\"relative flex items-center gap-2 rounded px-2.5 py-1.5 text-body-sm text-white/85 no-underline transition-colors hover:bg-white/10 hover:text-white\"\n [class.opacity-50]=\"child.disabled\"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"14\" />\n }\n {{ child.label }}\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: true,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"relative flex w-full items-center gap-2 rounded px-2.5 py-1.5 text-left text-body-sm text-white/85 transition-colors hover:bg-white/10 hover:text-white\"\n [disabled]=\"!!child.disabled\"\n (click)=\"onActivate(child, $event)\"\n >\n @if (child.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"14\" />\n }\n {{ child.label }}\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: child.badge,\n compact: true,\n }\"\n />\n </button>\n }\n </li>\n }\n </ul>\n }\n </div>\n }\n </li>\n }\n </ul>\n </nav>\n\n <ng-content select=\"[footer]\" />\n </aside>\n\n <ng-template #rowInner let-item>\n <span class=\"inline-flex size-5 shrink-0 items-center justify-center\">\n @if (item.icon; as icon) {\n <africanies-icon [name]=\"icon\" [size]=\"16\" />\n } @else {\n <span\n class=\"size-1.5 rounded-full bg-current opacity-70\"\n aria-hidden=\"true\"\n ></span>\n }\n </span>\n @if (!collapsed()) {\n <span class=\"min-w-0 flex-1 truncate text-left\">{{ item.label }}</span>\n @if (item.children?.length) {\n <africanies-icon\n name=\"chevron-down\"\n [size]=\"14\"\n class=\"shrink-0 opacity-70 transition-transform duration-200 ease-out\"\n [class.rotate-180]=\"isBranchOpen(item.id)\"\n />\n }\n }\n <ng-container\n [ngTemplateOutlet]=\"navBadge\"\n [ngTemplateOutletContext]=\"{\n visible: item.badge,\n compact: collapsed(),\n }\"\n />\n </ng-template>\n\n <ng-template #navBadge let-visible=\"visible\" let-compact=\"compact\">\n @if (visible) {\n <span\n class=\"pointer-events-none absolute z-10 overflow-visible\"\n [class]=\"compact ? 'right-0.5 top-0.5 h-2 w-2' : 'right-1.5 top-1 h-2 w-2'\"\n aria-hidden=\"true\"\n >\n <span\n class=\"africanies-side-nav-badge-ping absolute left-1/2 top-1/2 size-2 rounded-full bg-danger\"\n ></span>\n <span\n class=\"absolute left-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-danger ring-2 ring-white dark:ring-ink\"\n ></span>\n </span>\n }\n </ng-template>", styles: ["@keyframes africanies-side-nav-badge-pulse{0%{transform:translate(-50%,-50%) scale(.85);opacity:.9}70%{transform:translate(-50%,-50%) scale(2.4);opacity:0}to{transform:translate(-50%,-50%) scale(2.4);opacity:0}}.africanies-side-nav-badge-ping{animation:africanies-side-nav-badge-pulse 1.4s cubic-bezier(0,0,.2,1) infinite}@media(prefers-reduced-motion:reduce){.africanies-side-nav-badge-ping{animation:none;opacity:.5}}\n"] }]
14112
14195
  }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], activeId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeId", required: false }] }, { type: i0.Output, args: ["activeIdChange"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], expandParents: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandParents", required: false }] }], expandBranchesByDefault: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandBranchesByDefault", required: false }] }], linkActiveOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "linkActiveOptions", required: false }] }], showLogo: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLogo", required: false }] }], logoSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "logoSrc", required: false }] }], logoMiniSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "logoMiniSrc", required: false }] }] } });
14113
14196
 
14114
14197
  /**
@@ -14637,7 +14720,7 @@ class AppShellContentHeaderComponent {
14637
14720
  <div class="flex min-w-0 items-center gap-2 sm:gap-3">
14638
14721
  @if (showBackButton()) {
14639
14722
  <a
14640
- class="inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-lg text-neutral-600 transition-colors hover:bg-background-welcome hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
14723
+ class="inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-lg text-neutral-600 transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
14641
14724
  [attr.aria-label]="backLabel()"
14642
14725
  [routerLink]="backTarget()!.routerLink"
14643
14726
  [queryParams]="backTarget()!.queryParams"
@@ -14683,7 +14766,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
14683
14766
  <div class="flex min-w-0 items-center gap-2 sm:gap-3">
14684
14767
  @if (showBackButton()) {
14685
14768
  <a
14686
- class="inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-lg text-neutral-600 transition-colors hover:bg-background-welcome hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
14769
+ class="inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-lg text-neutral-600 transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
14687
14770
  [attr.aria-label]="backLabel()"
14688
14771
  [routerLink]="backTarget()!.routerLink"
14689
14772
  [queryParams]="backTarget()!.queryParams"
@@ -14856,7 +14939,7 @@ class AvatarMenuComponent {
14856
14939
  <button
14857
14940
  type="button"
14858
14941
  africaniesActionMenuTrigger
14859
- class="inline-flex cursor-pointer items-center gap-1 rounded-lg p-0.5 pr-1 transition-colors hover:bg-background-welcome focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-white/10"
14942
+ class="inline-flex cursor-pointer items-center gap-1 rounded-lg p-0.5 pr-1 transition-colors hover:bg-background-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-white/10"
14860
14943
  [attr.aria-label]="ariaLabel()"
14861
14944
  >
14862
14945
  <africanies-avatar [name]="name()" [src]="src()" [size]="size()" />
@@ -14864,7 +14947,7 @@ class AvatarMenuComponent {
14864
14947
  <africanies-icon
14865
14948
  name="chevron-down"
14866
14949
  [size]="14"
14867
- class="shrink-0 text-neutral-500 dark:text-neutral-400"
14950
+ class="shrink-0 text-neutral-600 dark:text-neutral-400"
14868
14951
  aria-hidden="true"
14869
14952
  />
14870
14953
  }
@@ -14893,7 +14976,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
14893
14976
  <button
14894
14977
  type="button"
14895
14978
  africaniesActionMenuTrigger
14896
- class="inline-flex cursor-pointer items-center gap-1 rounded-lg p-0.5 pr-1 transition-colors hover:bg-background-welcome focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-white/10"
14979
+ class="inline-flex cursor-pointer items-center gap-1 rounded-lg p-0.5 pr-1 transition-colors hover:bg-background-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-white/10"
14897
14980
  [attr.aria-label]="ariaLabel()"
14898
14981
  >
14899
14982
  <africanies-avatar [name]="name()" [src]="src()" [size]="size()" />
@@ -14901,7 +14984,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
14901
14984
  <africanies-icon
14902
14985
  name="chevron-down"
14903
14986
  [size]="14"
14904
- class="shrink-0 text-neutral-500 dark:text-neutral-400"
14987
+ class="shrink-0 text-neutral-600 dark:text-neutral-400"
14905
14988
  aria-hidden="true"
14906
14989
  />
14907
14990
  }
@@ -14934,14 +15017,15 @@ class NotificationDrawerPanel {
14934
15017
  viewLinkClass = computed(() => {
14935
15018
  const base = 'inline-flex cursor-pointer items-center gap-1 self-start text-caption font-semibold underline underline-offset-[3px] transition-colors';
14936
15019
  if (this.shipping.mode() === 'stn') {
14937
- return `${base} text-import decoration-import/35 hover:decoration-import dark:text-import-light dark:decoration-import-light/40 dark:hover:decoration-import-light`;
15020
+ return `${base} text-import-strong decoration-import-strong/35 hover:decoration-import-strong dark:text-import-light dark:decoration-import-light/40 dark:hover:decoration-import-light`;
14938
15021
  }
14939
- return `${base} text-export decoration-export/35 hover:decoration-export dark:text-export-light dark:decoration-export-light/40 dark:hover:decoration-export-light`;
15022
+ return `${base} text-export-strong decoration-export-strong/35 hover:decoration-export-strong dark:text-export-light dark:decoration-export-light/40 dark:hover:decoration-export-light`;
14940
15023
  }, /* @ts-ignore */
14941
15024
  ...(ngDevMode ? [{ debugName: "viewLinkClass" }] : /* istanbul ignore next */ []));
14942
15025
  title = computed(() => this.data.title ?? 'Notifications', /* @ts-ignore */
14943
15026
  ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
14944
- items = signal(this.data.onLoadPage ? [] : (this.data.notifications ?? []), /* @ts-ignore */
15027
+ seededItems = this.data.notifications ?? [];
15028
+ items = signal(this.seededItems, /* @ts-ignore */
14945
15029
  ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
14946
15030
  markingId = signal(null, /* @ts-ignore */
14947
15031
  ...(ngDevMode ? [{ debugName: "markingId" }] : /* istanbul ignore next */ []));
@@ -14949,7 +15033,7 @@ class NotificationDrawerPanel {
14949
15033
  ...(ngDevMode ? [{ debugName: "markingAll" }] : /* istanbul ignore next */ []));
14950
15034
  confirmingMarkAll = signal(false, /* @ts-ignore */
14951
15035
  ...(ngDevMode ? [{ debugName: "confirmingMarkAll" }] : /* istanbul ignore next */ []));
14952
- loadingInitial = signal(!!this.data.onLoadPage, /* @ts-ignore */
15036
+ loadingInitial = signal(!!this.data.onLoadPage && this.seededItems.length === 0, /* @ts-ignore */
14953
15037
  ...(ngDevMode ? [{ debugName: "loadingInitial" }] : /* istanbul ignore next */ []));
14954
15038
  loadingMore = signal(false, /* @ts-ignore */
14955
15039
  ...(ngDevMode ? [{ debugName: "loadingMore" }] : /* istanbul ignore next */ []));
@@ -14983,7 +15067,13 @@ class NotificationDrawerPanel {
14983
15067
  markAllConfirmTimer = null;
14984
15068
  constructor() {
14985
15069
  if (this.data.onLoadPage) {
14986
- this.fetchPage(1, 'replace');
15070
+ if (this.seededItems.length > 0) {
15071
+ this.currentPage.set(1);
15072
+ this.fetchPage(1, 'refresh');
15073
+ }
15074
+ else {
15075
+ this.fetchPage(1, 'replace');
15076
+ }
14987
15077
  }
14988
15078
  effect(() => {
14989
15079
  if (this.loadingInitial() ||
@@ -15177,11 +15267,14 @@ class NotificationDrawerPanel {
15177
15267
  this.loadingInitial.set(true);
15178
15268
  this.loadError.set(null);
15179
15269
  }
15270
+ else if (mode === 'refresh') {
15271
+ this.loadError.set(null);
15272
+ }
15180
15273
  else {
15181
15274
  this.loadingMore.set(true);
15182
15275
  }
15183
15276
  this.runPageLoad(() => loadPage(page), (result) => {
15184
- if (mode === 'replace') {
15277
+ if (mode === 'replace' || mode === 'refresh') {
15185
15278
  this.items.set(result.items ?? []);
15186
15279
  }
15187
15280
  else {
@@ -15196,6 +15289,9 @@ class NotificationDrawerPanel {
15196
15289
  this.loadError.set('Could not load notifications. Try again.');
15197
15290
  this.loadingInitial.set(false);
15198
15291
  }
15292
+ else if (mode === 'refresh' && this.items().length === 0) {
15293
+ this.loadError.set('Could not load notifications. Try again.');
15294
+ }
15199
15295
  this.loadingMore.set(false);
15200
15296
  });
15201
15297
  }
@@ -15269,7 +15365,7 @@ class NotificationDrawerPanel {
15269
15365
  {{ title() }}
15270
15366
  </h2>
15271
15367
  @if (hasUnread()) {
15272
- <p class="m-0 text-caption text-neutral-500 dark:text-neutral-400">
15368
+ <p class="m-0 text-caption text-neutral-600 dark:text-neutral-400">
15273
15369
  {{ unreadCount() }} unread
15274
15370
  </p>
15275
15371
  }
@@ -15315,7 +15411,7 @@ class NotificationDrawerPanel {
15315
15411
  <africanies-icon
15316
15412
  name="spinner"
15317
15413
  [size]="24"
15318
- class="animate-spin text-neutral-500 dark:text-neutral-400"
15414
+ class="animate-spin text-neutral-600 dark:text-neutral-400"
15319
15415
  />
15320
15416
  <p class="m-0 text-body-sm text-neutral-600 dark:text-neutral-400">
15321
15417
  Loading notifications…
@@ -15330,7 +15426,7 @@ class NotificationDrawerPanel {
15330
15426
  <button
15331
15427
  africanies-button
15332
15428
  type="button"
15333
- variant="secondary"
15429
+ variant="flat"
15334
15430
  size="sm"
15335
15431
  (click)="retryInitialLoad()"
15336
15432
  >
@@ -15382,7 +15478,7 @@ class NotificationDrawerPanel {
15382
15478
  </h3>
15383
15479
  @if (item.timestamp) {
15384
15480
  <time
15385
- class="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-neutral-500 dark:text-neutral-400"
15481
+ class="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-neutral-600 dark:text-neutral-400"
15386
15482
  [dateTime]="item.timestamp"
15387
15483
  >
15388
15484
  {{ formatTimestamp(item.timestamp) }}
@@ -15461,7 +15557,7 @@ class NotificationDrawerPanel {
15461
15557
  >
15462
15558
  @if (loadingMore()) {
15463
15559
  <span
15464
- class="inline-flex items-center gap-2 text-caption text-neutral-500 dark:text-neutral-400"
15560
+ class="inline-flex items-center gap-2 text-caption text-neutral-600 dark:text-neutral-400"
15465
15561
  aria-live="polite"
15466
15562
  >
15467
15563
  <africanies-icon name="spinner" [size]="14" class="animate-spin" />
@@ -15500,7 +15596,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
15500
15596
  {{ title() }}
15501
15597
  </h2>
15502
15598
  @if (hasUnread()) {
15503
- <p class="m-0 text-caption text-neutral-500 dark:text-neutral-400">
15599
+ <p class="m-0 text-caption text-neutral-600 dark:text-neutral-400">
15504
15600
  {{ unreadCount() }} unread
15505
15601
  </p>
15506
15602
  }
@@ -15546,7 +15642,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
15546
15642
  <africanies-icon
15547
15643
  name="spinner"
15548
15644
  [size]="24"
15549
- class="animate-spin text-neutral-500 dark:text-neutral-400"
15645
+ class="animate-spin text-neutral-600 dark:text-neutral-400"
15550
15646
  />
15551
15647
  <p class="m-0 text-body-sm text-neutral-600 dark:text-neutral-400">
15552
15648
  Loading notifications…
@@ -15561,7 +15657,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
15561
15657
  <button
15562
15658
  africanies-button
15563
15659
  type="button"
15564
- variant="secondary"
15660
+ variant="flat"
15565
15661
  size="sm"
15566
15662
  (click)="retryInitialLoad()"
15567
15663
  >
@@ -15613,7 +15709,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
15613
15709
  </h3>
15614
15710
  @if (item.timestamp) {
15615
15711
  <time
15616
- class="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-neutral-500 dark:text-neutral-400"
15712
+ class="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-neutral-600 dark:text-neutral-400"
15617
15713
  [dateTime]="item.timestamp"
15618
15714
  >
15619
15715
  {{ formatTimestamp(item.timestamp) }}
@@ -15692,7 +15788,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
15692
15788
  >
15693
15789
  @if (loadingMore()) {
15694
15790
  <span
15695
- class="inline-flex items-center gap-2 text-caption text-neutral-500 dark:text-neutral-400"
15791
+ class="inline-flex items-center gap-2 text-caption text-neutral-600 dark:text-neutral-400"
15696
15792
  aria-live="polite"
15697
15793
  >
15698
15794
  <africanies-icon name="spinner" [size]="14" class="animate-spin" />
@@ -15995,8 +16091,11 @@ function hashString(value) {
15995
16091
  return hash;
15996
16092
  }
15997
16093
 
15998
- const CACHE_KEY = 'africanies-header-weather-v3';
16094
+ /** Bump when cache shape or lookup strategy changes. */
16095
+ const CACHE_KEY = 'africanies-header-weather-v5';
15999
16096
  const FETCH_MS = 4_000;
16097
+ const BROWSER_GEO_MS = 3_500;
16098
+ /** IP fallback for coordinates only — city strings are often wrong. */
16000
16099
  const GEO_URL = 'https://get.geojs.io/v1/ip/geo.json';
16001
16100
  const WEATHER_LABELS = {
16002
16101
  clear: 'Clear',
@@ -16041,9 +16140,13 @@ function mapWmoWeatherCode(code) {
16041
16140
  * Short condition label for the header weather chip.
16042
16141
  *
16043
16142
  * @param kind - Coarse forecast kind.
16044
- * @returns Display label such as `Rain` or `Clear`.
16143
+ * @param hour - Local hour; clear days show as Sunny, nights as Clear.
16144
+ * @returns Display label such as `Rain` or `Sunny`.
16045
16145
  */
16046
- function headerWeatherLabel(kind) {
16146
+ function headerWeatherLabel(kind, hour = new Date().getHours()) {
16147
+ if (kind === 'clear') {
16148
+ return hour >= 19 || hour < 6 ? 'Clear' : 'Sunny';
16149
+ }
16047
16150
  return WEATHER_LABELS[kind];
16048
16151
  }
16049
16152
  /**
@@ -16063,7 +16166,12 @@ function headerWeatherIcon(kind, hour) {
16063
16166
  return 'cloud-o';
16064
16167
  }
16065
16168
  /**
16066
- * City-level forecast via IP geolocation + Open-Meteo (no API key).
16169
+ * Local forecast via browser / IP coordinates + Open-Meteo (no API key).
16170
+ *
16171
+ * Coordinates prefer the browser Geolocation API (may prompt once).
16172
+ * Otherwise IP geo is used for lat/lon only — city labels are not shown
16173
+ * because IP and reverse-geocode cities are often wrong (e.g. NG IPs
16174
+ * labelled Lagos).
16067
16175
  *
16068
16176
  * Fails closed: missing browser APIs, timeouts, and HTTP errors all return
16069
16177
  * `null` so the greeting can stay time-of-day only. Cached per local hour
@@ -16083,14 +16191,12 @@ async function loadHeaderWeather(fetchFn) {
16083
16191
  return cached;
16084
16192
  }
16085
16193
  try {
16086
- const geo = (await fetchJson(run, GEO_URL));
16087
- const latitude = asNumber(geo?.latitude);
16088
- const longitude = asNumber(geo?.longitude);
16089
- if (latitude === null || longitude === null) {
16194
+ const coords = await resolveCoordinates(run);
16195
+ if (!coords) {
16090
16196
  return null;
16091
16197
  }
16092
16198
  const forecastUrl = `https://api.open-meteo.com/v1/forecast` +
16093
- `?latitude=${latitude}&longitude=${longitude}` +
16199
+ `?latitude=${coords.latitude}&longitude=${coords.longitude}` +
16094
16200
  `&current=weather_code,temperature_2m`;
16095
16201
  const forecast = (await fetchJson(run, forecastUrl));
16096
16202
  const code = asNumber(forecast?.current?.weather_code);
@@ -16102,9 +16208,8 @@ async function loadHeaderWeather(fetchFn) {
16102
16208
  return null;
16103
16209
  }
16104
16210
  const temperatureC = asNumber(forecast?.current?.temperature_2m) ?? undefined;
16105
- const city = asCity(geo?.city);
16106
- const weather = { kind, temperatureC, city };
16107
- writeCache({ hour, kind, temperatureC, city });
16211
+ const weather = { kind, temperatureC };
16212
+ writeCache({ hour, kind, temperatureC });
16108
16213
  return weather;
16109
16214
  }
16110
16215
  catch {
@@ -16130,7 +16235,6 @@ function readCache(hour) {
16130
16235
  return {
16131
16236
  kind: parsed.kind,
16132
16237
  temperatureC: parsed.temperatureC,
16133
- city: parsed.city,
16134
16238
  };
16135
16239
  }
16136
16240
  catch {
@@ -16145,6 +16249,46 @@ function writeCache(value) {
16145
16249
  // Private mode / quota — greeting still works without cache.
16146
16250
  }
16147
16251
  }
16252
+ /**
16253
+ * Prefer device coordinates when available (may prompt once); otherwise fall
16254
+ * back to IP lat/lon for the forecast only.
16255
+ */
16256
+ async function resolveCoordinates(fetchFn) {
16257
+ const browser = await tryBrowserGeolocation();
16258
+ if (browser) {
16259
+ return browser;
16260
+ }
16261
+ const geo = (await fetchJson(fetchFn, GEO_URL));
16262
+ const latitude = asNumber(geo?.latitude);
16263
+ const longitude = asNumber(geo?.longitude);
16264
+ if (latitude === null || longitude === null) {
16265
+ return null;
16266
+ }
16267
+ return { latitude, longitude };
16268
+ }
16269
+ async function tryBrowserGeolocation() {
16270
+ if (typeof navigator === 'undefined' ||
16271
+ typeof navigator.geolocation?.getCurrentPosition !== 'function') {
16272
+ return null;
16273
+ }
16274
+ return await new Promise((resolve) => {
16275
+ const timer = setTimeout(() => resolve(null), BROWSER_GEO_MS);
16276
+ navigator.geolocation.getCurrentPosition((position) => {
16277
+ clearTimeout(timer);
16278
+ resolve({
16279
+ latitude: position.coords.latitude,
16280
+ longitude: position.coords.longitude,
16281
+ });
16282
+ }, () => {
16283
+ clearTimeout(timer);
16284
+ resolve(null);
16285
+ }, {
16286
+ enableHighAccuracy: false,
16287
+ maximumAge: 600_000,
16288
+ timeout: BROWSER_GEO_MS,
16289
+ });
16290
+ });
16291
+ }
16148
16292
  function asNumber(value) {
16149
16293
  if (typeof value === 'number' && Number.isFinite(value)) {
16150
16294
  return value;
@@ -16155,16 +16299,6 @@ function asNumber(value) {
16155
16299
  }
16156
16300
  return null;
16157
16301
  }
16158
- function asCity(value) {
16159
- if (typeof value !== 'string') {
16160
- return undefined;
16161
- }
16162
- const trimmed = value.trim();
16163
- if (!trimmed || trimmed.toLowerCase() === 'n/a') {
16164
- return undefined;
16165
- }
16166
- return trimmed;
16167
- }
16168
16302
  async function fetchJson(fetchFn, url) {
16169
16303
  const controller = new AbortController();
16170
16304
  const timer = setTimeout(() => controller.abort(), FETCH_MS);
@@ -16297,27 +16431,27 @@ class AppShellHeaderComponent {
16297
16431
  if (temp === undefined || !Number.isFinite(temp)) {
16298
16432
  return null;
16299
16433
  }
16300
- return temp.toFixed(1);
16434
+ return String(Math.round(temp));
16301
16435
  }, /* @ts-ignore */
16302
16436
  ...(ngDevMode ? [{ debugName: "weatherTemp" }] : /* istanbul ignore next */ []));
16303
- weatherPlace = computed(() => {
16437
+ weatherCondition = computed(() => {
16304
16438
  const forecast = this.weather();
16305
16439
  if (!forecast) {
16306
16440
  return '';
16307
16441
  }
16308
- return forecast.city?.trim() || headerWeatherLabel(forecast.kind);
16442
+ return headerWeatherLabel(forecast.kind, this.now().getHours());
16309
16443
  }, /* @ts-ignore */
16310
- ...(ngDevMode ? [{ debugName: "weatherPlace" }] : /* istanbul ignore next */ []));
16444
+ ...(ngDevMode ? [{ debugName: "weatherCondition" }] : /* istanbul ignore next */ []));
16311
16445
  weatherAriaLabel = computed(() => {
16312
16446
  const forecast = this.weather();
16313
16447
  if (!forecast) {
16314
16448
  return 'Weather';
16315
16449
  }
16316
- const condition = headerWeatherLabel(forecast.kind).toLowerCase();
16450
+ const condition = this.weatherCondition().toLowerCase();
16317
16451
  const temp = this.weatherTemp();
16318
- const city = forecast.city?.trim();
16319
- const degrees = temp === null ? condition : `${temp} degrees, ${condition}`;
16320
- return city ? `Weather in ${city}: ${degrees}` : `Weather: ${degrees}`;
16452
+ return temp === null
16453
+ ? `Weather: ${condition}`
16454
+ : `Weather: ${temp} degrees Celsius, ${condition}`;
16321
16455
  }, /* @ts-ignore */
16322
16456
  ...(ngDevMode ? [{ debugName: "weatherAriaLabel" }] : /* istanbul ignore next */ []));
16323
16457
  unreadCount = computed(() => (this.notifications() ?? []).filter((n) => !n.read).length, /* @ts-ignore */
@@ -16366,7 +16500,7 @@ class AppShellHeaderComponent {
16366
16500
  this.notificationsDrawer
16367
16501
  .open({
16368
16502
  title: this.notificationsTitle(),
16369
- notifications: onLoadPage ? undefined : this.notifications(),
16503
+ notifications: this.notifications(),
16370
16504
  onLoadPage,
16371
16505
  onMarkRead,
16372
16506
  onMarkAllRead,
@@ -16386,7 +16520,7 @@ class AppShellHeaderComponent {
16386
16520
  @if (greeting(); as hello) {
16387
16521
  <div class="min-w-0 overflow-hidden">
16388
16522
  <p
16389
- class="m-0 truncate text-caption leading-none text-neutral-500 dark:text-neutral-400"
16523
+ class="m-0 truncate text-caption leading-none text-neutral-600 dark:text-neutral-400"
16390
16524
  >
16391
16525
  {{ hello.kicker }}
16392
16526
  </p>
@@ -16412,19 +16546,19 @@ class AppShellHeaderComponent {
16412
16546
  <africanies-icon
16413
16547
  [name]="weatherIcon()"
16414
16548
  [size]="18"
16415
- class="text-neutral-500 dark:text-neutral-400"
16549
+ class="text-neutral-600 dark:text-neutral-400"
16416
16550
  />
16417
16551
  @if (weatherTemp(); as temp) {
16418
16552
  <span
16419
16553
  class="text-body-sm font-medium tabular-nums tracking-tight text-ink dark:text-white"
16420
16554
  >
16421
- {{ temp }}°
16555
+ {{ temp }}°C
16422
16556
  </span>
16423
16557
  }
16424
16558
  <span
16425
- class="hidden text-caption text-neutral-500 md:inline dark:text-neutral-400"
16559
+ class="hidden text-caption text-neutral-600 md:inline dark:text-neutral-400"
16426
16560
  >
16427
- {{ weatherPlace() }}
16561
+ {{ weatherCondition() }}
16428
16562
  </span>
16429
16563
  </div>
16430
16564
  }
@@ -16432,7 +16566,7 @@ class AppShellHeaderComponent {
16432
16566
  @if (showClock()) {
16433
16567
  <div class="flex items-baseline gap-2" aria-live="polite">
16434
16568
  <time
16435
- class="hidden text-caption text-neutral-500 md:inline dark:text-neutral-400"
16569
+ class="hidden text-caption text-neutral-600 md:inline dark:text-neutral-400"
16436
16570
  [dateTime]="nowIso()"
16437
16571
  >
16438
16572
  {{ now() | date: clockDateFormat() }}
@@ -16462,7 +16596,7 @@ class AppShellHeaderComponent {
16462
16596
  @if (showNotifications()) {
16463
16597
  <button
16464
16598
  type="button"
16465
- class="relative inline-flex size-9 cursor-pointer items-center justify-center rounded-lg text-neutral-600 transition-colors hover:bg-background-welcome hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
16599
+ class="relative inline-flex size-9 cursor-pointer items-center justify-center rounded-lg text-neutral-600 transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
16466
16600
  [attr.aria-label]="notificationAriaLabel()"
16467
16601
  (click)="openNotifications()"
16468
16602
  >
@@ -16513,7 +16647,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16513
16647
  @if (greeting(); as hello) {
16514
16648
  <div class="min-w-0 overflow-hidden">
16515
16649
  <p
16516
- class="m-0 truncate text-caption leading-none text-neutral-500 dark:text-neutral-400"
16650
+ class="m-0 truncate text-caption leading-none text-neutral-600 dark:text-neutral-400"
16517
16651
  >
16518
16652
  {{ hello.kicker }}
16519
16653
  </p>
@@ -16539,19 +16673,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16539
16673
  <africanies-icon
16540
16674
  [name]="weatherIcon()"
16541
16675
  [size]="18"
16542
- class="text-neutral-500 dark:text-neutral-400"
16676
+ class="text-neutral-600 dark:text-neutral-400"
16543
16677
  />
16544
16678
  @if (weatherTemp(); as temp) {
16545
16679
  <span
16546
16680
  class="text-body-sm font-medium tabular-nums tracking-tight text-ink dark:text-white"
16547
16681
  >
16548
- {{ temp }}°
16682
+ {{ temp }}°C
16549
16683
  </span>
16550
16684
  }
16551
16685
  <span
16552
- class="hidden text-caption text-neutral-500 md:inline dark:text-neutral-400"
16686
+ class="hidden text-caption text-neutral-600 md:inline dark:text-neutral-400"
16553
16687
  >
16554
- {{ weatherPlace() }}
16688
+ {{ weatherCondition() }}
16555
16689
  </span>
16556
16690
  </div>
16557
16691
  }
@@ -16559,7 +16693,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16559
16693
  @if (showClock()) {
16560
16694
  <div class="flex items-baseline gap-2" aria-live="polite">
16561
16695
  <time
16562
- class="hidden text-caption text-neutral-500 md:inline dark:text-neutral-400"
16696
+ class="hidden text-caption text-neutral-600 md:inline dark:text-neutral-400"
16563
16697
  [dateTime]="nowIso()"
16564
16698
  >
16565
16699
  {{ now() | date: clockDateFormat() }}
@@ -16589,7 +16723,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16589
16723
  @if (showNotifications()) {
16590
16724
  <button
16591
16725
  type="button"
16592
- class="relative inline-flex size-9 cursor-pointer items-center justify-center rounded-lg text-neutral-600 transition-colors hover:bg-background-welcome hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
16726
+ class="relative inline-flex size-9 cursor-pointer items-center justify-center rounded-lg text-neutral-600 transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
16593
16727
  [attr.aria-label]="notificationAriaLabel()"
16594
16728
  (click)="openNotifications()"
16595
16729
  >
@@ -16763,8 +16897,8 @@ class AppShellComponent {
16763
16897
  hostClass = computed(() => this.layoutPreview() ? 'block h-full' : 'block min-h-full', /* @ts-ignore */
16764
16898
  ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
16765
16899
  shellRootClass = computed(() => this.layoutPreview()
16766
- ? 'relative flex h-full min-h-0'
16767
- : 'relative flex min-h-full', /* @ts-ignore */
16900
+ ? 'relative flex h-full min-h-0 bg-background-chrome dark:bg-ink-950'
16901
+ : 'relative flex min-h-full bg-background-chrome dark:bg-ink-950', /* @ts-ignore */
16768
16902
  ...(ngDevMode ? [{ debugName: "shellRootClass" }] : /* istanbul ignore next */ []));
16769
16903
  headerDensity = computed(() => {
16770
16904
  const preview = this.layoutPreview();
@@ -16791,14 +16925,14 @@ class AppShellComponent {
16791
16925
  // Preview embeds: absolute/h-full so the drawer stays inside the frame.
16792
16926
  // Production: h-dvh so the rail fills the viewport (h-full only matches parent).
16793
16927
  if (preview === 'desktop') {
16794
- return 'z-50 shrink-0 self-start sticky top-0 block h-full';
16928
+ return 'z-50 shrink-0 self-start sticky top-0 block h-full min-h-0';
16795
16929
  }
16796
16930
  if (preview === 'mobile' || preview === 'tablet') {
16797
16931
  return open
16798
- ? 'z-50 shrink-0 self-start absolute inset-y-0 left-0 h-full shadow-xl'
16932
+ ? 'z-50 shrink-0 self-start absolute inset-y-0 left-0 h-full min-h-0 shadow-xl'
16799
16933
  : 'hidden';
16800
16934
  }
16801
- const base = 'z-50 shrink-0 self-start h-dvh';
16935
+ const base = 'z-50 shrink-0 self-start h-dvh min-h-0';
16802
16936
  if (open) {
16803
16937
  return `${base} fixed inset-y-0 left-0 shadow-xl lg:sticky lg:top-0 lg:block`;
16804
16938
  }
@@ -16834,13 +16968,13 @@ class AppShellComponent {
16834
16968
  this.mobileNavOpen.set(false);
16835
16969
  }
16836
16970
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AppShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16837
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AppShellComponent, isStandalone: true, selector: "africanies-app-shell", inputs: { contentWidth: { classPropertyName: "contentWidth", publicName: "contentWidth", isSignal: true, isRequired: false, transformFunction: null }, showAccent: { classPropertyName: "showAccent", publicName: "showAccent", isSignal: true, isRequired: false, transformFunction: null }, showHeaderChrome: { classPropertyName: "showHeaderChrome", publicName: "showHeaderChrome", isSignal: true, isRequired: false, transformFunction: null }, layoutPreview: { classPropertyName: "layoutPreview", publicName: "layoutPreview", isSignal: true, isRequired: false, transformFunction: null }, headerTitle: { classPropertyName: "headerTitle", publicName: "headerTitle", isSignal: true, isRequired: false, transformFunction: null }, headerSubtitle: { classPropertyName: "headerSubtitle", publicName: "headerSubtitle", isSignal: true, isRequired: false, transformFunction: null }, breadcrumbs: { classPropertyName: "breadcrumbs", publicName: "breadcrumbs", isSignal: true, isRequired: false, transformFunction: null }, catalogNav: { classPropertyName: "catalogNav", publicName: "catalogNav", isSignal: true, isRequired: false, transformFunction: null }, showContentBack: { classPropertyName: "showContentBack", publicName: "showContentBack", isSignal: true, isRequired: false, transformFunction: null }, contentBackLink: { classPropertyName: "contentBackLink", publicName: "contentBackLink", isSignal: true, isRequired: false, transformFunction: null }, contentBackLabel: { classPropertyName: "contentBackLabel", publicName: "contentBackLabel", isSignal: true, isRequired: false, transformFunction: null }, userName: { classPropertyName: "userName", publicName: "userName", isSignal: true, isRequired: false, transformFunction: null }, greetingName: { classPropertyName: "greetingName", publicName: "greetingName", isSignal: true, isRequired: false, transformFunction: null }, userAvatarSrc: { classPropertyName: "userAvatarSrc", publicName: "userAvatarSrc", isSignal: true, isRequired: false, transformFunction: null }, userMenuItems: { classPropertyName: "userMenuItems", publicName: "userMenuItems", isSignal: true, isRequired: false, transformFunction: null }, notifications: { classPropertyName: "notifications", publicName: "notifications", isSignal: true, isRequired: false, transformFunction: null }, notificationsTitle: { classPropertyName: "notificationsTitle", publicName: "notificationsTitle", isSignal: true, isRequired: false, transformFunction: null }, showClock: { classPropertyName: "showClock", publicName: "showClock", isSignal: true, isRequired: false, transformFunction: null }, showNotifications: { classPropertyName: "showNotifications", publicName: "showNotifications", isSignal: true, isRequired: false, transformFunction: null }, clockDateFormat: { classPropertyName: "clockDateFormat", publicName: "clockDateFormat", isSignal: true, isRequired: false, transformFunction: null }, clockFormat: { classPropertyName: "clockFormat", publicName: "clockFormat", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClass()" } }, queries: [{ propertyName: "customHeader", first: true, predicate: AppShellHeaderSlotDirective, descendants: true, isSignal: true }], ngImport: i0, template: " <div [class]=\"shellRootClass()\">\n @if (mobileNavOpen()) {\n <button\n type=\"button\"\n class=\"absolute inset-0 z-40 cursor-pointer bg-ink/40\"\n [class.lg:hidden]=\"!layoutPreview()\"\n aria-label=\"Close navigation\"\n (click)=\"closeMobileNav()\"\n ></button>\n }\n\n <div [class]=\"sidenavHostClass()\">\n <ng-content select=\"[sidenav]\" />\n </div>\n\n <div class=\"flex min-w-0 flex-1 flex-col\">\n @if (showMobileBar()) {\n <div\n class=\"flex shrink-0 items-center gap-2 border-b border-border px-3 py-2 dark:border-white/10\"\n [class.lg:hidden]=\"!layoutPreview()\"\n >\n <button\n type=\"button\"\n class=\"inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-lg border border-border text-ink transition-colors hover:bg-background-welcome dark:border-white/15 dark:text-white dark:hover:bg-white/10\"\n [attr.aria-label]=\"mobileNavOpen() ? 'Close navigation' : 'Open navigation'\"\n [attr.aria-expanded]=\"mobileNavOpen()\"\n (click)=\"toggleMobileNav()\"\n >\n <africanies-icon name=\"align-justify\" [size]=\"18\" />\n </button>\n <img\n [src]=\"logoMiniUrl\"\n alt=\"African Import Export Solutions\"\n class=\"h-7 w-auto object-contain\"\n />\n </div>\n }\n\n <div\n class=\"sticky top-0 z-30 border-b border-border bg-white/90 backdrop-blur-md dark:border-white/10 dark:bg-ink-950/90\"\n >\n @if (hasCustomHeader()) {\n <ng-content select=\"[africaniesAppShellHeader]\" />\n } @else if (showHeaderChrome()) {\n <africanies-app-shell-header\n [userName]=\"userName()\"\n [greetingName]=\"greetingName()\"\n [userAvatarSrc]=\"userAvatarSrc()\"\n [userMenuItems]=\"userMenuItems()\"\n [notifications]=\"notifications()\"\n [notificationsTitle]=\"notificationsTitle()\"\n [showClock]=\"showClock()\"\n [showNotifications]=\"showNotifications()\"\n [clockDateFormat]=\"clockDateFormat()\"\n [clockFormat]=\"clockFormat()\"\n [density]=\"headerDensity()\"\n />\n }\n @if (showAccent()) {\n <div\n class=\"h-0.5 w-full transition-colors duration-300\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n }\n </div>\n\n <main [class]=\"mainClass()\">\n @if (showContentChrome()) {\n <africanies-app-shell-content-header\n [breadcrumbs]=\"breadcrumbs()\"\n [title]=\"headerTitle()\"\n [subtitle]=\"headerSubtitle()\"\n [url]=\"currentUrl()\"\n [catalogNav]=\"catalogNav()\"\n [showBack]=\"showContentBack()\"\n [backLink]=\"contentBackLink()\"\n [backLabel]=\"contentBackLabel()\"\n />\n }\n <ng-content />\n </main>\n </div>\n </div>", dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: AppShellContentHeaderComponent, selector: "africanies-app-shell-content-header", inputs: ["breadcrumbs", "title", "subtitle", "url", "catalogNav", "showBack", "backLink", "backLabel"] }, { kind: "component", type: AppShellHeaderComponent, selector: "africanies-app-shell-header", inputs: ["userName", "greetingName", "userAvatarSrc", "userMenuItems", "notifications", "notificationsTitle", "onNotificationMarkRead", "onNotificationMarkAllRead", "onNotificationLoadPage", "showClock", "showNotifications", "density", "clockDateFormat", "clockFormat"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16971
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AppShellComponent, isStandalone: true, selector: "africanies-app-shell", inputs: { contentWidth: { classPropertyName: "contentWidth", publicName: "contentWidth", isSignal: true, isRequired: false, transformFunction: null }, showAccent: { classPropertyName: "showAccent", publicName: "showAccent", isSignal: true, isRequired: false, transformFunction: null }, showHeaderChrome: { classPropertyName: "showHeaderChrome", publicName: "showHeaderChrome", isSignal: true, isRequired: false, transformFunction: null }, layoutPreview: { classPropertyName: "layoutPreview", publicName: "layoutPreview", isSignal: true, isRequired: false, transformFunction: null }, headerTitle: { classPropertyName: "headerTitle", publicName: "headerTitle", isSignal: true, isRequired: false, transformFunction: null }, headerSubtitle: { classPropertyName: "headerSubtitle", publicName: "headerSubtitle", isSignal: true, isRequired: false, transformFunction: null }, breadcrumbs: { classPropertyName: "breadcrumbs", publicName: "breadcrumbs", isSignal: true, isRequired: false, transformFunction: null }, catalogNav: { classPropertyName: "catalogNav", publicName: "catalogNav", isSignal: true, isRequired: false, transformFunction: null }, showContentBack: { classPropertyName: "showContentBack", publicName: "showContentBack", isSignal: true, isRequired: false, transformFunction: null }, contentBackLink: { classPropertyName: "contentBackLink", publicName: "contentBackLink", isSignal: true, isRequired: false, transformFunction: null }, contentBackLabel: { classPropertyName: "contentBackLabel", publicName: "contentBackLabel", isSignal: true, isRequired: false, transformFunction: null }, userName: { classPropertyName: "userName", publicName: "userName", isSignal: true, isRequired: false, transformFunction: null }, greetingName: { classPropertyName: "greetingName", publicName: "greetingName", isSignal: true, isRequired: false, transformFunction: null }, userAvatarSrc: { classPropertyName: "userAvatarSrc", publicName: "userAvatarSrc", isSignal: true, isRequired: false, transformFunction: null }, userMenuItems: { classPropertyName: "userMenuItems", publicName: "userMenuItems", isSignal: true, isRequired: false, transformFunction: null }, notifications: { classPropertyName: "notifications", publicName: "notifications", isSignal: true, isRequired: false, transformFunction: null }, notificationsTitle: { classPropertyName: "notificationsTitle", publicName: "notificationsTitle", isSignal: true, isRequired: false, transformFunction: null }, showClock: { classPropertyName: "showClock", publicName: "showClock", isSignal: true, isRequired: false, transformFunction: null }, showNotifications: { classPropertyName: "showNotifications", publicName: "showNotifications", isSignal: true, isRequired: false, transformFunction: null }, clockDateFormat: { classPropertyName: "clockDateFormat", publicName: "clockDateFormat", isSignal: true, isRequired: false, transformFunction: null }, clockFormat: { classPropertyName: "clockFormat", publicName: "clockFormat", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClass()" } }, queries: [{ propertyName: "customHeader", first: true, predicate: AppShellHeaderSlotDirective, descendants: true, isSignal: true }], ngImport: i0, template: " <div [class]=\"shellRootClass()\">\n @if (mobileNavOpen()) {\n <button\n type=\"button\"\n class=\"absolute inset-0 z-40 cursor-pointer bg-ink/40\"\n [class.lg:hidden]=\"!layoutPreview()\"\n aria-label=\"Close navigation\"\n (click)=\"closeMobileNav()\"\n ></button>\n }\n\n <div [class]=\"sidenavHostClass()\">\n <ng-content select=\"[sidenav]\" />\n </div>\n\n <div class=\"flex min-w-0 flex-1 flex-col\">\n @if (showMobileBar()) {\n <div\n class=\"flex shrink-0 items-center gap-2 border-b border-border px-3 py-2 dark:border-white/10\"\n [class.lg:hidden]=\"!layoutPreview()\"\n >\n <button\n type=\"button\"\n class=\"inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-lg border border-control text-ink transition-colors hover:bg-background-hover dark:border-white/15 dark:text-white dark:hover:bg-white/10\"\n [attr.aria-label]=\"mobileNavOpen() ? 'Close navigation' : 'Open navigation'\"\n [attr.aria-expanded]=\"mobileNavOpen()\"\n (click)=\"toggleMobileNav()\"\n >\n <africanies-icon name=\"align-justify\" [size]=\"18\" />\n </button>\n <img\n [src]=\"logoMiniUrl\"\n alt=\"African Import Export Solutions\"\n class=\"h-7 w-auto object-contain\"\n />\n </div>\n }\n\n <div\n class=\"sticky top-0 z-30 border-b border-border bg-surface/90 backdrop-blur-md dark:border-white/10 dark:bg-ink-950/90\"\n >\n @if (hasCustomHeader()) {\n <ng-content select=\"[africaniesAppShellHeader]\" />\n } @else if (showHeaderChrome()) {\n <africanies-app-shell-header\n [userName]=\"userName()\"\n [greetingName]=\"greetingName()\"\n [userAvatarSrc]=\"userAvatarSrc()\"\n [userMenuItems]=\"userMenuItems()\"\n [notifications]=\"notifications()\"\n [notificationsTitle]=\"notificationsTitle()\"\n [showClock]=\"showClock()\"\n [showNotifications]=\"showNotifications()\"\n [clockDateFormat]=\"clockDateFormat()\"\n [clockFormat]=\"clockFormat()\"\n [density]=\"headerDensity()\"\n />\n }\n @if (showAccent()) {\n <div\n class=\"h-0.5 w-full transition-colors duration-300\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n }\n </div>\n\n <main [class]=\"mainClass()\">\n @if (showContentChrome()) {\n <africanies-app-shell-content-header\n [breadcrumbs]=\"breadcrumbs()\"\n [title]=\"headerTitle()\"\n [subtitle]=\"headerSubtitle()\"\n [url]=\"currentUrl()\"\n [catalogNav]=\"catalogNav()\"\n [showBack]=\"showContentBack()\"\n [backLink]=\"contentBackLink()\"\n [backLabel]=\"contentBackLabel()\"\n />\n }\n <ng-content />\n </main>\n </div>\n </div>", dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: AppShellContentHeaderComponent, selector: "africanies-app-shell-content-header", inputs: ["breadcrumbs", "title", "subtitle", "url", "catalogNav", "showBack", "backLink", "backLabel"] }, { kind: "component", type: AppShellHeaderComponent, selector: "africanies-app-shell-header", inputs: ["userName", "greetingName", "userAvatarSrc", "userMenuItems", "notifications", "notificationsTitle", "onNotificationMarkRead", "onNotificationMarkAllRead", "onNotificationLoadPage", "showClock", "showNotifications", "density", "clockDateFormat", "clockFormat"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16838
16972
  }
16839
16973
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AppShellComponent, decorators: [{
16840
16974
  type: Component,
16841
16975
  args: [{ selector: 'africanies-app-shell', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AfricaniesIconComponent, AppShellContentHeaderComponent, AppShellHeaderComponent], host: {
16842
16976
  '[class]': 'hostClass()',
16843
- }, template: " <div [class]=\"shellRootClass()\">\n @if (mobileNavOpen()) {\n <button\n type=\"button\"\n class=\"absolute inset-0 z-40 cursor-pointer bg-ink/40\"\n [class.lg:hidden]=\"!layoutPreview()\"\n aria-label=\"Close navigation\"\n (click)=\"closeMobileNav()\"\n ></button>\n }\n\n <div [class]=\"sidenavHostClass()\">\n <ng-content select=\"[sidenav]\" />\n </div>\n\n <div class=\"flex min-w-0 flex-1 flex-col\">\n @if (showMobileBar()) {\n <div\n class=\"flex shrink-0 items-center gap-2 border-b border-border px-3 py-2 dark:border-white/10\"\n [class.lg:hidden]=\"!layoutPreview()\"\n >\n <button\n type=\"button\"\n class=\"inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-lg border border-border text-ink transition-colors hover:bg-background-welcome dark:border-white/15 dark:text-white dark:hover:bg-white/10\"\n [attr.aria-label]=\"mobileNavOpen() ? 'Close navigation' : 'Open navigation'\"\n [attr.aria-expanded]=\"mobileNavOpen()\"\n (click)=\"toggleMobileNav()\"\n >\n <africanies-icon name=\"align-justify\" [size]=\"18\" />\n </button>\n <img\n [src]=\"logoMiniUrl\"\n alt=\"African Import Export Solutions\"\n class=\"h-7 w-auto object-contain\"\n />\n </div>\n }\n\n <div\n class=\"sticky top-0 z-30 border-b border-border bg-white/90 backdrop-blur-md dark:border-white/10 dark:bg-ink-950/90\"\n >\n @if (hasCustomHeader()) {\n <ng-content select=\"[africaniesAppShellHeader]\" />\n } @else if (showHeaderChrome()) {\n <africanies-app-shell-header\n [userName]=\"userName()\"\n [greetingName]=\"greetingName()\"\n [userAvatarSrc]=\"userAvatarSrc()\"\n [userMenuItems]=\"userMenuItems()\"\n [notifications]=\"notifications()\"\n [notificationsTitle]=\"notificationsTitle()\"\n [showClock]=\"showClock()\"\n [showNotifications]=\"showNotifications()\"\n [clockDateFormat]=\"clockDateFormat()\"\n [clockFormat]=\"clockFormat()\"\n [density]=\"headerDensity()\"\n />\n }\n @if (showAccent()) {\n <div\n class=\"h-0.5 w-full transition-colors duration-300\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n }\n </div>\n\n <main [class]=\"mainClass()\">\n @if (showContentChrome()) {\n <africanies-app-shell-content-header\n [breadcrumbs]=\"breadcrumbs()\"\n [title]=\"headerTitle()\"\n [subtitle]=\"headerSubtitle()\"\n [url]=\"currentUrl()\"\n [catalogNav]=\"catalogNav()\"\n [showBack]=\"showContentBack()\"\n [backLink]=\"contentBackLink()\"\n [backLabel]=\"contentBackLabel()\"\n />\n }\n <ng-content />\n </main>\n </div>\n </div>" }]
16977
+ }, template: " <div [class]=\"shellRootClass()\">\n @if (mobileNavOpen()) {\n <button\n type=\"button\"\n class=\"absolute inset-0 z-40 cursor-pointer bg-ink/40\"\n [class.lg:hidden]=\"!layoutPreview()\"\n aria-label=\"Close navigation\"\n (click)=\"closeMobileNav()\"\n ></button>\n }\n\n <div [class]=\"sidenavHostClass()\">\n <ng-content select=\"[sidenav]\" />\n </div>\n\n <div class=\"flex min-w-0 flex-1 flex-col\">\n @if (showMobileBar()) {\n <div\n class=\"flex shrink-0 items-center gap-2 border-b border-border px-3 py-2 dark:border-white/10\"\n [class.lg:hidden]=\"!layoutPreview()\"\n >\n <button\n type=\"button\"\n class=\"inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-lg border border-control text-ink transition-colors hover:bg-background-hover dark:border-white/15 dark:text-white dark:hover:bg-white/10\"\n [attr.aria-label]=\"mobileNavOpen() ? 'Close navigation' : 'Open navigation'\"\n [attr.aria-expanded]=\"mobileNavOpen()\"\n (click)=\"toggleMobileNav()\"\n >\n <africanies-icon name=\"align-justify\" [size]=\"18\" />\n </button>\n <img\n [src]=\"logoMiniUrl\"\n alt=\"African Import Export Solutions\"\n class=\"h-7 w-auto object-contain\"\n />\n </div>\n }\n\n <div\n class=\"sticky top-0 z-30 border-b border-border bg-surface/90 backdrop-blur-md dark:border-white/10 dark:bg-ink-950/90\"\n >\n @if (hasCustomHeader()) {\n <ng-content select=\"[africaniesAppShellHeader]\" />\n } @else if (showHeaderChrome()) {\n <africanies-app-shell-header\n [userName]=\"userName()\"\n [greetingName]=\"greetingName()\"\n [userAvatarSrc]=\"userAvatarSrc()\"\n [userMenuItems]=\"userMenuItems()\"\n [notifications]=\"notifications()\"\n [notificationsTitle]=\"notificationsTitle()\"\n [showClock]=\"showClock()\"\n [showNotifications]=\"showNotifications()\"\n [clockDateFormat]=\"clockDateFormat()\"\n [clockFormat]=\"clockFormat()\"\n [density]=\"headerDensity()\"\n />\n }\n @if (showAccent()) {\n <div\n class=\"h-0.5 w-full transition-colors duration-300\"\n [class]=\"modeColor.classes().bg\"\n aria-hidden=\"true\"\n ></div>\n }\n </div>\n\n <main [class]=\"mainClass()\">\n @if (showContentChrome()) {\n <africanies-app-shell-content-header\n [breadcrumbs]=\"breadcrumbs()\"\n [title]=\"headerTitle()\"\n [subtitle]=\"headerSubtitle()\"\n [url]=\"currentUrl()\"\n [catalogNav]=\"catalogNav()\"\n [showBack]=\"showContentBack()\"\n [backLink]=\"contentBackLink()\"\n [backLabel]=\"contentBackLabel()\"\n />\n }\n <ng-content />\n </main>\n </div>\n </div>" }]
16844
16978
  }], propDecorators: { customHeader: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AppShellHeaderSlotDirective), { isSignal: true }] }], contentWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentWidth", required: false }] }], showAccent: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAccent", required: false }] }], showHeaderChrome: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeaderChrome", required: false }] }], layoutPreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "layoutPreview", required: false }] }], headerTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerTitle", required: false }] }], headerSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerSubtitle", required: false }] }], breadcrumbs: [{ type: i0.Input, args: [{ isSignal: true, alias: "breadcrumbs", required: false }] }], catalogNav: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalogNav", required: false }] }], showContentBack: [{ type: i0.Input, args: [{ isSignal: true, alias: "showContentBack", required: false }] }], contentBackLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentBackLink", required: false }] }], contentBackLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentBackLabel", required: false }] }], userName: [{ type: i0.Input, args: [{ isSignal: true, alias: "userName", required: false }] }], greetingName: [{ type: i0.Input, args: [{ isSignal: true, alias: "greetingName", required: false }] }], userAvatarSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "userAvatarSrc", required: false }] }], userMenuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "userMenuItems", required: false }] }], notifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "notifications", required: false }] }], notificationsTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "notificationsTitle", required: false }] }], showClock: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClock", required: false }] }], showNotifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNotifications", required: false }] }], clockDateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "clockDateFormat", required: false }] }], clockFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "clockFormat", required: false }] }] } });
16845
16979
 
16846
16980
  /**
@@ -16968,7 +17102,7 @@ class ImageComponent {
16968
17102
 
16969
17103
  @if (showPlaceholder()) {
16970
17104
  <div
16971
- class="absolute inset-0 flex items-center justify-center bg-neutral-200 text-neutral-500 dark:bg-white/10 dark:text-neutral-400"
17105
+ class="absolute inset-0 flex items-center justify-center bg-neutral-200 text-neutral-600 dark:bg-white/10 dark:text-neutral-400"
16972
17106
  [class.animate-pulse]="pending()"
16973
17107
  aria-hidden="true"
16974
17108
  >
@@ -17024,7 +17158,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17024
17158
 
17025
17159
  @if (showPlaceholder()) {
17026
17160
  <div
17027
- class="absolute inset-0 flex items-center justify-center bg-neutral-200 text-neutral-500 dark:bg-white/10 dark:text-neutral-400"
17161
+ class="absolute inset-0 flex items-center justify-center bg-neutral-200 text-neutral-600 dark:bg-white/10 dark:text-neutral-400"
17028
17162
  [class.animate-pulse]="pending()"
17029
17163
  aria-hidden="true"
17030
17164
  >
@@ -17368,7 +17502,7 @@ class PaginationComponent {
17368
17502
  <button
17369
17503
  africanies-button
17370
17504
  type="button"
17371
- variant="secondary"
17505
+ variant="flat"
17372
17506
  size="sm"
17373
17507
  [disabled]="controlsDisabled() || !meta().has_previous_page"
17374
17508
  (click)="emitPage(meta().current_page - 1)"
@@ -17378,7 +17512,7 @@ class PaginationComponent {
17378
17512
  @for (item of pageItems(); track trackPageItem(item, $index)) {
17379
17513
  @if (item === 'ellipsis') {
17380
17514
  <span
17381
- class="inline-flex min-w-8 items-center justify-center text-body-sm text-neutral-500 dark:text-neutral-400"
17515
+ class="inline-flex min-w-8 items-center justify-center text-body-sm text-neutral-600 dark:text-neutral-400"
17382
17516
  aria-hidden="true"
17383
17517
  >
17384
17518
 
@@ -17389,7 +17523,7 @@ class PaginationComponent {
17389
17523
  type="button"
17390
17524
  size="sm"
17391
17525
  class="min-w-8"
17392
- [variant]="item === meta().current_page ? 'primary' : 'secondary'"
17526
+ [variant]="item === meta().current_page ? 'primary' : 'flat'"
17393
17527
  [disabled]="controlsDisabled()"
17394
17528
  [attr.aria-label]="'Page ' + item"
17395
17529
  [attr.aria-current]="
@@ -17404,7 +17538,7 @@ class PaginationComponent {
17404
17538
  <button
17405
17539
  africanies-button
17406
17540
  type="button"
17407
- variant="secondary"
17541
+ variant="flat"
17408
17542
  size="sm"
17409
17543
  [disabled]="controlsDisabled() || !meta().has_next_page"
17410
17544
  (click)="emitPage(meta().current_page + 1)"
@@ -17469,7 +17603,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17469
17603
  <button
17470
17604
  africanies-button
17471
17605
  type="button"
17472
- variant="secondary"
17606
+ variant="flat"
17473
17607
  size="sm"
17474
17608
  [disabled]="controlsDisabled() || !meta().has_previous_page"
17475
17609
  (click)="emitPage(meta().current_page - 1)"
@@ -17479,7 +17613,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17479
17613
  @for (item of pageItems(); track trackPageItem(item, $index)) {
17480
17614
  @if (item === 'ellipsis') {
17481
17615
  <span
17482
- class="inline-flex min-w-8 items-center justify-center text-body-sm text-neutral-500 dark:text-neutral-400"
17616
+ class="inline-flex min-w-8 items-center justify-center text-body-sm text-neutral-600 dark:text-neutral-400"
17483
17617
  aria-hidden="true"
17484
17618
  >
17485
17619
 
@@ -17490,7 +17624,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17490
17624
  type="button"
17491
17625
  size="sm"
17492
17626
  class="min-w-8"
17493
- [variant]="item === meta().current_page ? 'primary' : 'secondary'"
17627
+ [variant]="item === meta().current_page ? 'primary' : 'flat'"
17494
17628
  [disabled]="controlsDisabled()"
17495
17629
  [attr.aria-label]="'Page ' + item"
17496
17630
  [attr.aria-current]="
@@ -17505,7 +17639,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17505
17639
  <button
17506
17640
  africanies-button
17507
17641
  type="button"
17508
- variant="secondary"
17642
+ variant="flat"
17509
17643
  size="sm"
17510
17644
  [disabled]="controlsDisabled() || !meta().has_next_page"
17511
17645
  (click)="emitPage(meta().current_page + 1)"
@@ -17529,11 +17663,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17529
17663
  * Hidden while the body shows empty or error — those states expose Retry instead.
17530
17664
  * While {@link refreshing} is true the rows stay on screen — the refresh icon
17531
17665
  * spins (do not swap to a blocking loader). Use {@link loading} when the page
17532
- * of data is changing (pagination / size): rows stay on screen and the built-in
17533
- * pager shows a circular spinner beside the page controls; the body loader
17534
- * appears only when there are no rows yet (first load) or the host cleared
17535
- * rows for a shipping-mode switch (STN SFN). Toolbar and column headers stay
17536
- * mounted.
17666
+ * of data is changing (pagination / size): rows stay on screen, a keep-rows
17667
+ * overlay covers the grid, and the built-in pager shows a circular spinner
17668
+ * beside the page controls; the body loader appears only when there are no
17669
+ * rows yet (first load) or the host cleared rows for a shipping-mode switch
17670
+ * (STN ↔ SFN). Toolbar and column headers stay mounted.
17537
17671
  * top-right **Filters** then **Export** (`showFilter` / `showExport`). The table
17538
17672
  * does not own fetch, filter, or export logic — the host handles those events.
17539
17673
  *
@@ -17590,9 +17724,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17590
17724
  * **Sticky columns:** a column with `key: 'actions'` stays pinned to the
17591
17725
  * right while the grid scrolls horizontally. The expand trigger column pins
17592
17726
  * to the left when rows are expandable. Override with
17593
- * {@link TableColumn.sticky}.
17727
+ * {@link TableColumn.sticky}. Sticky columns use a slightly thicker bottom
17728
+ * border (and opaque fill) so dividers stay visible while scrolling.
17729
+ *
17730
+ * **Sticky header (desktop):** when {@link stickyHeader} is true (default),
17731
+ * large screens scroll the grid inside a capped viewport and keep `th` pinned
17732
+ * to the top of that scrollport. Mobile keeps horizontal-only overflow so
17733
+ * page scroll is unchanged.
17594
17734
  */
17595
17735
  class TableComponent {
17736
+ modeColor = inject(ModeColorService);
17596
17737
  /**
17597
17738
  * When false, hides row expansion even if {@link RowDetailDefDirective}
17598
17739
  * templates are projected. Defaults to true.
@@ -17608,6 +17749,12 @@ class TableComponent {
17608
17749
  /** Column definitions (header chrome + keys). */
17609
17750
  columns = input.required(/* @ts-ignore */
17610
17751
  ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
17752
+ /**
17753
+ * When true (default), pin column headers while scrolling the grid on
17754
+ * large screens (`lg+`). Uses a capped scrollport so sticky works with
17755
+ * horizontal overflow. Set false to keep the previous unconstrained layout.
17756
+ */
17757
+ stickyHeader = input(true, { ...(ngDevMode ? { debugName: "stickyHeader" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
17611
17758
  /**
17612
17759
  * Row records to render. Empty arrays show {@link EmptyStateComponent} in
17613
17760
  * the body unless {@link loading} or {@link error} is set. Missing `data`
@@ -17638,9 +17785,9 @@ class TableComponent {
17638
17785
  refreshing = input(false, { ...(ngDevMode ? { debugName: "refreshing" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
17639
17786
  /**
17640
17787
  * Page of data is loading (first load, shipping-mode switch, pagination,
17641
- * page size). When rows are already on screen, the pager shows a spinner
17642
- * and rows stay visible; when there are no rows (first load or the host
17643
- * dropped STN/SFN rows after a mode switch), the body shows
17788
+ * page size). When rows are already on screen, a keep-rows overlay covers
17789
+ * the grid and the pager shows a spinner; when there are no rows (first
17790
+ * load or the host dropped STN/SFN rows after a mode switch), the body shows
17644
17791
  * {@link LoadingStateComponent}. Distinct from {@link refreshing}.
17645
17792
  */
17646
17793
  loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
@@ -17802,6 +17949,21 @@ class TableComponent {
17802
17949
  cellContext(row) {
17803
17950
  return { $implicit: row };
17804
17951
  }
17952
+ /**
17953
+ * Header row chrome. Soft fill lives on each `th` ({@link headerSoftClass})
17954
+ * so sticky columns do not double-tint and paint over neighbor labels.
17955
+ * @returns Header row class list.
17956
+ */
17957
+ headerRowClass() {
17958
+ return 'border-b border-border dark:border-white/10';
17959
+ }
17960
+ /**
17961
+ * Opaque mode soft fill for header cells (sticky-safe in dark mode).
17962
+ * @returns Soft solid background utilities.
17963
+ */
17964
+ headerSoftClass() {
17965
+ return this.modeColor.classes().softSolid;
17966
+ }
17805
17967
  /**
17806
17968
  * Body row classes — row marker for hover styles, plus hide cell borders on
17807
17969
  * the last / expanded row.
@@ -17812,7 +17974,7 @@ class TableComponent {
17812
17974
  bodyRowClass(row, index) {
17813
17975
  if (this.isExpandable() && this.isRowExpanded(row, index)) {
17814
17976
  // Only the bottom edge — keep the top divider so the row still
17815
- // separates from the row above.
17977
+ // separates from the row above. Detail panel owns the separator.
17816
17978
  return 'africanies-table-row group [&>td]:border-b-0';
17817
17979
  }
17818
17980
  return 'africanies-table-row group last:[&>td]:border-b-0';
@@ -17838,21 +18000,27 @@ class TableComponent {
17838
18000
  * @returns Header class list.
17839
18001
  */
17840
18002
  expandHeaderClass() {
17841
- return ('w-10 border-b border-border px-2 py-3.5 dark:border-white/10 ' +
18003
+ return ('w-10 border-b-[1.5px] border-border px-2 py-4 dark:border-white/10 ' +
18004
+ this.headerSoftClass() +
18005
+ ' ' +
17842
18006
  this.stickyPinClass('left', 'head'));
17843
18007
  }
17844
18008
  /**
17845
18009
  * Body classes for the expand/collapse trigger column (pinned left).
17846
- * Omits `border-b` like other sticky cells so the opaque fill does not
17847
- * double-paint the row divider.
18010
+ * Uses a slightly thicker bottom border than scrolling cells so the
18011
+ * sticky fill still reads a clear divider (no top-shadow fallback).
17848
18012
  * @param index - Index within {@link rows}.
17849
18013
  * @returns Body cell class list.
17850
18014
  */
17851
18015
  expandBodyClass(index) {
17852
- const prev = index > 0 ? this.rowList()[index - 1] : undefined;
17853
- const afterExpanded = prev !== undefined && this.isRowExpanded(prev, index - 1);
17854
- return ('w-10 px-2 py-3.5 align-middle ' +
17855
- this.stickyPinClass('left', 'body', index, afterExpanded));
18016
+ const row = this.rowList()[index];
18017
+ const expanded = row !== undefined && this.isRowExpanded(row, index);
18018
+ const border = expanded
18019
+ ? ''
18020
+ : 'border-b-[1.5px] border-border dark:border-white/10 ';
18021
+ return ('w-10 px-2 py-4 align-middle ' +
18022
+ border +
18023
+ this.stickyPinClass('left', 'body'));
17856
18024
  }
17857
18025
  /**
17858
18026
  * Header cell classes, including sticky pin when set.
@@ -17860,69 +18028,86 @@ class TableComponent {
17860
18028
  * @returns Header class list.
17861
18029
  */
17862
18030
  headerCellClass(col) {
17863
- return ('border-b border-border whitespace-normal wrap-break-word px-3 py-3.5 font-medium text-neutral-600 dark:border-white/10 dark:text-neutral-400 ' +
17864
- this.stickySurfaceClass(col, 'head'));
18031
+ const sticky = this.stickyEdge(col);
18032
+ const border = sticky
18033
+ ? 'border-b-[1.5px] '
18034
+ : 'border-b ';
18035
+ return (border +
18036
+ 'border-border whitespace-normal wrap-break-word px-[1.125rem] py-4 font-medium text-neutral-600 dark:border-white/10 dark:text-neutral-400 ' +
18037
+ this.headerSoftClass() +
18038
+ ' ' +
18039
+ this.stickySurfaceClass(col, 'head') +
18040
+ (sticky ? '' : this.headerStickClass()));
18041
+ }
18042
+ /**
18043
+ * Vertical pin for scrolling header cells on desktop. Pinned left/right
18044
+ * headers get `lg:top-0` from {@link stickyPinClass} instead.
18045
+ */
18046
+ headerStickClass() {
18047
+ if (!this.stickyHeader()) {
18048
+ return '';
18049
+ }
18050
+ return ' lg:sticky lg:top-0 lg:z-[3] print:static';
17865
18051
  }
17866
18052
  /**
17867
18053
  * Body cell classes, including sticky pin when set.
17868
- * Sticky cells omit `border-b`: their opaque fill sits in a higher stacking
17869
- * context, so a bottom border plus the next row's top shadow paint on the
17870
- * same pixel and read brighter (especially in dark mode).
18054
+ * Sticky expand/actions columns use a 1.5px bottom border; scrolling cells
18055
+ * stay at 1px. Expanded rows drop the bottom border (see {@link bodyRowClass}).
17871
18056
  * @param col - Column definition.
17872
18057
  * @param index - Index within {@link rows}.
17873
18058
  * @returns Body cell class list.
17874
18059
  */
17875
18060
  bodyCellClass(col, index) {
17876
- const rowLine = this.stickyEdge(col)
18061
+ const row = this.rowList()[index];
18062
+ const expanded = row !== undefined && this.isRowExpanded(row, index);
18063
+ const sticky = this.stickyEdge(col);
18064
+ const rowLine = expanded
17877
18065
  ? ''
17878
- : 'border-b border-border dark:border-white/10 ';
17879
- const prev = index > 0 ? this.rowList()[index - 1] : undefined;
17880
- const afterExpanded = prev !== undefined && this.isRowExpanded(prev, index - 1);
18066
+ : sticky
18067
+ ? 'border-b-[1.5px] border-border dark:border-white/10 '
18068
+ : 'border-b border-border dark:border-white/10 ';
17881
18069
  const wrap = col.key === 'actions'
17882
18070
  ? 'whitespace-nowrap '
17883
18071
  : 'whitespace-normal wrap-break-word ';
17884
18072
  return (rowLine +
17885
- 'px-3 py-3.5 align-middle ' +
18073
+ 'px-[1.125rem] py-4 align-middle ' +
17886
18074
  wrap +
17887
- this.stickySurfaceClass(col, 'body', index, afterExpanded));
18075
+ this.stickySurfaceClass(col, 'body'));
17888
18076
  }
17889
18077
  /**
17890
18078
  * Sticky pin utilities for a known edge (expand column or data column).
17891
18079
  * @param edge - Left or right pin.
17892
18080
  * @param surface - Header vs body fill tokens.
17893
- * @param index - Body row index; unused for the header.
17894
- * @param afterExpanded - Previous row is expanded — skip the top shadow
17895
- * divider so the sticky column stays flush under the detail panel.
17896
18081
  * @returns Sticky utilities.
17897
18082
  */
17898
- stickyPinClass(edge, surface, index = 0, afterExpanded = false) {
18083
+ stickyPinClass(edge, surface) {
17899
18084
  // Expand (left) sits above scrolling cells; actions (right) match.
17900
18085
  // Header needs a higher z so it stays above sticky body cells.
17901
18086
  const side = edge === 'right' ? 'sticky right-0 z-[1]' : 'sticky left-0 z-[1]';
17902
18087
  if (surface === 'head') {
17903
- return `${side} z-[2] bg-background-welcome dark:bg-ink-950`;
18088
+ // Fill comes from {@link headerSoftClass} on the cell — do not re-tint.
18089
+ // Corner pins also stick vertically on desktop when stickyHeader is on.
18090
+ const vertical = this.stickyHeader()
18091
+ ? ' z-[2] lg:top-0 lg:z-[4] print:static'
18092
+ : ' z-[2]';
18093
+ return `${side}${vertical}`;
17904
18094
  }
17905
- const rowDivider = !afterExpanded && index > 0
17906
- ? 'shadow-[0_-1px_0_0_#c9d5e1] dark:shadow-[0_-1px_0_0_rgba(255,255,255,0.1)]'
17907
- : '';
17908
- const fill = 'bg-white dark:bg-ink';
17909
- return `${side} ${fill} ${rowDivider}`;
18095
+ const fill = 'bg-surface dark:bg-ink-surface';
18096
+ return `${side} ${fill}`;
17910
18097
  }
17911
18098
  /**
17912
18099
  * Sticky pin plus an opaque fill matching the table surface, so scrolled
17913
18100
  * cells do not show through.
17914
18101
  * @param col - Column definition.
17915
18102
  * @param surface - Header vs body fill tokens.
17916
- * @param index - Body row index; unused for the header.
17917
- * @param afterExpanded - Previous row is expanded — skip the top shadow.
17918
18103
  * @returns Sticky utilities, or empty when the column scrolls.
17919
18104
  */
17920
- stickySurfaceClass(col, surface, index = 0, afterExpanded = false) {
18105
+ stickySurfaceClass(col, surface) {
17921
18106
  const edge = this.stickyEdge(col);
17922
18107
  if (!edge) {
17923
18108
  return '';
17924
18109
  }
17925
- return this.stickyPinClass(edge, surface, index, afterExpanded);
18110
+ return this.stickyPinClass(edge, surface);
17926
18111
  }
17927
18112
  /**
17928
18113
  * Stable id for a row — used for expand/collapse state.
@@ -18013,7 +18198,7 @@ class TableComponent {
18013
18198
  this.sortChange.emit({ key, direction });
18014
18199
  }
18015
18200
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18016
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: TableComponent, isStandalone: true, selector: "africanies-table", inputs: { expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, rowTrackBy: { classPropertyName: "rowTrackBy", publicName: "rowTrackBy", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, showRefresh: { classPropertyName: "showRefresh", publicName: "showRefresh", isSignal: true, isRequired: false, transformFunction: null }, refreshLabel: { classPropertyName: "refreshLabel", publicName: "refreshLabel", isSignal: true, isRequired: false, transformFunction: null }, refreshing: { classPropertyName: "refreshing", publicName: "refreshing", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingLabel: { classPropertyName: "loadingLabel", publicName: "loadingLabel", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, showFilter: { classPropertyName: "showFilter", publicName: "showFilter", isSignal: true, isRequired: false, transformFunction: null }, filterLabel: { classPropertyName: "filterLabel", publicName: "filterLabel", isSignal: true, isRequired: false, transformFunction: null }, filterCount: { classPropertyName: "filterCount", publicName: "filterCount", isSignal: true, isRequired: false, transformFunction: null }, showExport: { classPropertyName: "showExport", publicName: "showExport", isSignal: true, isRequired: false, transformFunction: null }, exportLabel: { classPropertyName: "exportLabel", publicName: "exportLabel", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowExpandChange: "rowExpandChange", sortChange: "sortChange", refreshClick: "refreshClick", filterClick: "filterClick", exportClick: "exportClick", pageChange: "pageChange", sizeChange: "sizeChange" }, host: { classAttribute: "block w-full min-w-0" }, queries: [{ propertyName: "cellDefs", predicate: CellDefDirective, isSignal: true }, { propertyName: "headerCellDefs", predicate: HeaderCellDefDirective, isSignal: true }, { propertyName: "rowDetailDefs", predicate: RowDetailDefDirective, isSignal: true }], ngImport: i0, template: `
18201
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: TableComponent, isStandalone: true, selector: "africanies-table", inputs: { expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, rowTrackBy: { classPropertyName: "rowTrackBy", publicName: "rowTrackBy", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, showRefresh: { classPropertyName: "showRefresh", publicName: "showRefresh", isSignal: true, isRequired: false, transformFunction: null }, refreshLabel: { classPropertyName: "refreshLabel", publicName: "refreshLabel", isSignal: true, isRequired: false, transformFunction: null }, refreshing: { classPropertyName: "refreshing", publicName: "refreshing", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingLabel: { classPropertyName: "loadingLabel", publicName: "loadingLabel", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, showFilter: { classPropertyName: "showFilter", publicName: "showFilter", isSignal: true, isRequired: false, transformFunction: null }, filterLabel: { classPropertyName: "filterLabel", publicName: "filterLabel", isSignal: true, isRequired: false, transformFunction: null }, filterCount: { classPropertyName: "filterCount", publicName: "filterCount", isSignal: true, isRequired: false, transformFunction: null }, showExport: { classPropertyName: "showExport", publicName: "showExport", isSignal: true, isRequired: false, transformFunction: null }, exportLabel: { classPropertyName: "exportLabel", publicName: "exportLabel", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowExpandChange: "rowExpandChange", sortChange: "sortChange", refreshClick: "refreshClick", filterClick: "filterClick", exportClick: "exportClick", pageChange: "pageChange", sizeChange: "sizeChange" }, host: { classAttribute: "block w-full min-w-0" }, queries: [{ propertyName: "cellDefs", predicate: CellDefDirective, isSignal: true }, { propertyName: "headerCellDefs", predicate: HeaderCellDefDirective, isSignal: true }, { propertyName: "rowDetailDefs", predicate: RowDetailDefDirective, isSignal: true }], ngImport: i0, template: `
18017
18202
  <div
18018
18203
  class="flex w-full min-w-0 flex-col gap-3"
18019
18204
  [attr.aria-busy]="loading() || refreshing() || null"
@@ -18025,7 +18210,7 @@ class TableComponent {
18025
18210
  <button
18026
18211
  africanies-button
18027
18212
  type="button"
18028
- variant="secondary"
18213
+ variant="flat"
18029
18214
  size="sm"
18030
18215
  [disabled]="loading() || refreshing()"
18031
18216
  [attr.aria-label]="refreshLabel()"
@@ -18045,7 +18230,7 @@ class TableComponent {
18045
18230
  <button
18046
18231
  africanies-button
18047
18232
  type="button"
18048
- variant="secondary"
18233
+ variant="flat"
18049
18234
  size="sm"
18050
18235
  [attr.aria-label]="filterLabel()"
18051
18236
  (click)="filterClick.emit()"
@@ -18065,7 +18250,7 @@ class TableComponent {
18065
18250
  <button
18066
18251
  africanies-button
18067
18252
  type="button"
18068
- variant="secondary"
18253
+ variant="flat"
18069
18254
  size="sm"
18070
18255
  [attr.aria-label]="exportLabel()"
18071
18256
  (click)="exportClick.emit()"
@@ -18079,26 +18264,24 @@ class TableComponent {
18079
18264
  }
18080
18265
 
18081
18266
  @if (staleError(); as staleMessage) {
18082
- <div class="flex justify-end">
18083
- <africanies-error-indicator
18084
- class="max-w-[min(100%,20rem)]"
18085
- [error]="staleMessage"
18086
- retryText="Refresh"
18087
- [refreshing]="refreshing()"
18088
- (retry)="refreshClick.emit()"
18089
- />
18090
- </div>
18267
+ <africanies-error-indicator
18268
+ class="w-full"
18269
+ [error]="staleMessage"
18270
+ retryText="Refresh"
18271
+ [refreshing]="refreshing()"
18272
+ (retry)="refreshClick.emit()"
18273
+ />
18091
18274
  }
18092
18275
 
18093
18276
  <div
18094
- class="min-w-0 w-full overflow-x-auto rounded-md border border-border bg-white dark:border-white/10 dark:bg-ink"
18277
+ class="relative min-w-0 w-full overflow-x-auto rounded-panel border border-border bg-surface dark:border-white/10 dark:bg-ink-surface"
18278
+ [class.lg:max-h-[min(70dvh,calc(100dvh-12rem))]]="stickyHeader()"
18279
+ [class.lg:overflow-auto]="stickyHeader()"
18095
18280
  >
18096
18281
  <table
18097
18282
  class="w-max min-w-full table-auto border-separate border-spacing-0 bg-inherit text-left text-body text-ink dark:text-white"
18098
18283
  >
18099
- <thead
18100
- class="border-b border-border bg-background-welcome dark:border-white/10 dark:bg-ink-950"
18101
- >
18284
+ <thead [class]="headerRowClass()">
18102
18285
  <tr>
18103
18286
  @if (isExpandable()) {
18104
18287
  <th
@@ -18108,7 +18291,7 @@ class TableComponent {
18108
18291
  @if (someRowsExpanded()) {
18109
18292
  <button
18110
18293
  type="button"
18111
- class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-white hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18294
+ class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-surface hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18112
18295
  aria-label="Collapse all"
18113
18296
  (click)="collapseAllRows()"
18114
18297
  >
@@ -18117,7 +18300,7 @@ class TableComponent {
18117
18300
  } @else {
18118
18301
  <button
18119
18302
  type="button"
18120
- class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-white hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink disabled:cursor-not-allowed disabled:opacity-40 dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18303
+ class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-surface hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus disabled:cursor-not-allowed disabled:opacity-40 dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18121
18304
  aria-label="Expand all"
18122
18305
  [disabled]="rowList().length === 0"
18123
18306
  (click)="expandAllRows()"
@@ -18131,7 +18314,8 @@ class TableComponent {
18131
18314
  <th
18132
18315
  scope="col"
18133
18316
  [class]="headerCellClass(col)"
18134
- [style.width]="col.width ?? null"
18317
+ [style.width]="col.width ?? col.maxWidth ?? null"
18318
+ [style.max-width]="col.maxWidth ?? null"
18135
18319
  >
18136
18320
  @if (headerTemplateFor(col.key); as headerTpl) {
18137
18321
  <ng-container
@@ -18140,7 +18324,7 @@ class TableComponent {
18140
18324
  } @else if (col.sortable) {
18141
18325
  <button
18142
18326
  type="button"
18143
- class="inline-flex cursor-pointer items-center gap-1 rounded-sm font-medium text-neutral-600 hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:text-white"
18327
+ class="inline-flex cursor-pointer items-center gap-1 rounded-sm font-medium text-neutral-600 hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:text-white"
18144
18328
  (click)="onSortHeaderClick(col.key)"
18145
18329
  >
18146
18330
  <span>{{ col.header }}</span>
@@ -18162,14 +18346,14 @@ class TableComponent {
18162
18346
  @switch (bodyKind()) {
18163
18347
  @case ('loading') {
18164
18348
  <tr>
18165
- <td class="px-3 py-6" [attr.colspan]="colSpan()">
18349
+ <td class="px-[1.125rem] py-6" [attr.colspan]="colSpan()">
18166
18350
  <africanies-loading-state [message]="loadingLabel()" />
18167
18351
  </td>
18168
18352
  </tr>
18169
18353
  }
18170
18354
  @case ('error') {
18171
18355
  <tr>
18172
- <td class="px-3 py-6" [attr.colspan]="colSpan()">
18356
+ <td class="px-[1.125rem] py-6" [attr.colspan]="colSpan()">
18173
18357
  <africanies-error-state
18174
18358
  [message]="errorMessage()"
18175
18359
  [refreshing]="refreshing()"
@@ -18180,7 +18364,7 @@ class TableComponent {
18180
18364
  }
18181
18365
  @case ('empty') {
18182
18366
  <tr>
18183
- <td class="px-3 py-6" [attr.colspan]="colSpan()">
18367
+ <td class="px-[1.125rem] py-6" [attr.colspan]="colSpan()">
18184
18368
  <africanies-empty-state
18185
18369
  [message]="emptyMessage()"
18186
18370
  [refreshing]="refreshing()"
@@ -18196,7 +18380,7 @@ class TableComponent {
18196
18380
  <td [class]="expandBodyClass(i)">
18197
18381
  <button
18198
18382
  type="button"
18199
- class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-background-welcome hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18383
+ class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18200
18384
  [attr.aria-label]="
18201
18385
  isRowExpanded(row, i)
18202
18386
  ? 'Collapse row details'
@@ -18215,7 +18399,8 @@ class TableComponent {
18215
18399
  @for (col of columns(); track col.key) {
18216
18400
  <td
18217
18401
  [class]="bodyCellClass(col, i)"
18218
- [style.width]="col.width ?? null"
18402
+ [style.width]="col.width ?? col.maxWidth ?? null"
18403
+ [style.max-width]="col.maxWidth ?? null"
18219
18404
  >
18220
18405
  @if (templateFor(col.key); as tpl) {
18221
18406
  <ng-container
@@ -18245,17 +18430,17 @@ class TableComponent {
18245
18430
  [class.pointer-events-none]="!isRowExpanded(row, i)"
18246
18431
  >
18247
18432
  <div
18248
- class="min-h-0 overflow-hidden border-t border-border/70 bg-neutral-50 dark:border-white/10 dark:bg-white/[0.05]"
18433
+ class="min-h-0 overflow-hidden border-t border-border-strong bg-surface-sunken dark:border-white/10 dark:bg-white/[0.05]"
18249
18434
  >
18250
18435
  <dl
18251
- class="m-0 grid gap-x-6 gap-y-3 px-3 py-3 transition-opacity duration-200 ease-out motion-reduce:transition-none sm:grid-cols-2 lg:grid-cols-3"
18436
+ class="m-0 grid gap-x-6 gap-y-3 px-[1.125rem] py-4 transition-opacity duration-200 ease-out motion-reduce:transition-none sm:grid-cols-2 lg:grid-cols-3"
18252
18437
  [class.opacity-0]="!isRowExpanded(row, i)"
18253
18438
  [class.opacity-100]="isRowExpanded(row, i)"
18254
18439
  >
18255
18440
  @for (detail of rowDetailDefs(); track detail.label()) {
18256
18441
  <div class="min-w-0">
18257
18442
  <dt
18258
- class="m-0 text-caption font-medium text-neutral-500 dark:text-neutral-400"
18443
+ class="m-0 text-caption font-medium text-neutral-600 dark:text-neutral-400"
18259
18444
  >
18260
18445
  {{ detail.label() }}
18261
18446
  </dt>
@@ -18280,6 +18465,18 @@ class TableComponent {
18280
18465
  }
18281
18466
  </tbody>
18282
18467
  </table>
18468
+ @if (paginationLoading()) {
18469
+ <div
18470
+ class="pointer-events-none absolute inset-0 z-[5] flex items-start justify-center bg-surface/70 px-4 pt-16 dark:bg-ink-950/70"
18471
+ data-testid="africanies-table-keep-rows-loading"
18472
+ aria-hidden="true"
18473
+ >
18474
+ <africanies-loading-state
18475
+ mode="inline"
18476
+ [message]="loadingLabel()"
18477
+ />
18478
+ </div>
18479
+ }
18283
18480
  </div>
18284
18481
 
18285
18482
  @if (meta(); as pager) {
@@ -18292,7 +18489,7 @@ class TableComponent {
18292
18489
  />
18293
18490
  }
18294
18491
  </div>
18295
- `, isInline: true, styles: [":host tr.africanies-table-row:hover>td{background-color:#eef2f6}:host-context(.dark) tr.africanies-table-row:hover>td{background-color:#272729}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ButtonComponent, selector: "button[africanies-button], a[africanies-button]", inputs: ["variant", "size", "disabled", "loading"] }, { kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: EmptyStateComponent, selector: "africanies-empty-state", inputs: ["message", "refreshing"], outputs: ["retry"] }, { kind: "component", type: ErrorIndicatorComponent, selector: "africanies-error-indicator", inputs: ["error", "retryText", "refreshing", "refreshingText", "disabled"], outputs: ["retry"] }, { kind: "component", type: ErrorStateComponent, selector: "africanies-error-state", inputs: ["message", "refreshing"], outputs: ["retry"] }, { kind: "component", type: LoadingStateComponent, selector: "africanies-loading-state", inputs: ["message", "mode"] }, { kind: "component", type: PaginationComponent, selector: "africanies-pagination", inputs: ["meta", "disabled", "loading", "pageSizes"], outputs: ["pageChange", "sizeChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18492
+ `, isInline: true, styles: [":host tr.africanies-table-row:hover>td{background-color:#e9eef5}:host-context(.dark) tr.africanies-table-row:hover>td{background-color:#272729}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ButtonComponent, selector: "button[africanies-button], a[africanies-button]", inputs: ["variant", "size", "disabled", "loading"] }, { kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: EmptyStateComponent, selector: "africanies-empty-state", inputs: ["message", "refreshing"], outputs: ["retry"] }, { kind: "component", type: ErrorIndicatorComponent, selector: "africanies-error-indicator", inputs: ["error", "retryText", "refreshing", "refreshingText", "disabled"], outputs: ["retry"] }, { kind: "component", type: ErrorStateComponent, selector: "africanies-error-state", inputs: ["message", "refreshing"], outputs: ["retry"] }, { kind: "component", type: LoadingStateComponent, selector: "africanies-loading-state", inputs: ["message", "mode"] }, { kind: "component", type: PaginationComponent, selector: "africanies-pagination", inputs: ["meta", "disabled", "loading", "pageSizes"], outputs: ["pageChange", "sizeChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18296
18493
  }
18297
18494
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TableComponent, decorators: [{
18298
18495
  type: Component,
@@ -18319,7 +18516,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18319
18516
  <button
18320
18517
  africanies-button
18321
18518
  type="button"
18322
- variant="secondary"
18519
+ variant="flat"
18323
18520
  size="sm"
18324
18521
  [disabled]="loading() || refreshing()"
18325
18522
  [attr.aria-label]="refreshLabel()"
@@ -18339,7 +18536,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18339
18536
  <button
18340
18537
  africanies-button
18341
18538
  type="button"
18342
- variant="secondary"
18539
+ variant="flat"
18343
18540
  size="sm"
18344
18541
  [attr.aria-label]="filterLabel()"
18345
18542
  (click)="filterClick.emit()"
@@ -18359,7 +18556,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18359
18556
  <button
18360
18557
  africanies-button
18361
18558
  type="button"
18362
- variant="secondary"
18559
+ variant="flat"
18363
18560
  size="sm"
18364
18561
  [attr.aria-label]="exportLabel()"
18365
18562
  (click)="exportClick.emit()"
@@ -18373,26 +18570,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18373
18570
  }
18374
18571
 
18375
18572
  @if (staleError(); as staleMessage) {
18376
- <div class="flex justify-end">
18377
- <africanies-error-indicator
18378
- class="max-w-[min(100%,20rem)]"
18379
- [error]="staleMessage"
18380
- retryText="Refresh"
18381
- [refreshing]="refreshing()"
18382
- (retry)="refreshClick.emit()"
18383
- />
18384
- </div>
18573
+ <africanies-error-indicator
18574
+ class="w-full"
18575
+ [error]="staleMessage"
18576
+ retryText="Refresh"
18577
+ [refreshing]="refreshing()"
18578
+ (retry)="refreshClick.emit()"
18579
+ />
18385
18580
  }
18386
18581
 
18387
18582
  <div
18388
- class="min-w-0 w-full overflow-x-auto rounded-md border border-border bg-white dark:border-white/10 dark:bg-ink"
18583
+ class="relative min-w-0 w-full overflow-x-auto rounded-panel border border-border bg-surface dark:border-white/10 dark:bg-ink-surface"
18584
+ [class.lg:max-h-[min(70dvh,calc(100dvh-12rem))]]="stickyHeader()"
18585
+ [class.lg:overflow-auto]="stickyHeader()"
18389
18586
  >
18390
18587
  <table
18391
18588
  class="w-max min-w-full table-auto border-separate border-spacing-0 bg-inherit text-left text-body text-ink dark:text-white"
18392
18589
  >
18393
- <thead
18394
- class="border-b border-border bg-background-welcome dark:border-white/10 dark:bg-ink-950"
18395
- >
18590
+ <thead [class]="headerRowClass()">
18396
18591
  <tr>
18397
18592
  @if (isExpandable()) {
18398
18593
  <th
@@ -18402,7 +18597,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18402
18597
  @if (someRowsExpanded()) {
18403
18598
  <button
18404
18599
  type="button"
18405
- class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-white hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18600
+ class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-surface hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18406
18601
  aria-label="Collapse all"
18407
18602
  (click)="collapseAllRows()"
18408
18603
  >
@@ -18411,7 +18606,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18411
18606
  } @else {
18412
18607
  <button
18413
18608
  type="button"
18414
- class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-white hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink disabled:cursor-not-allowed disabled:opacity-40 dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18609
+ class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-surface hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus disabled:cursor-not-allowed disabled:opacity-40 dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18415
18610
  aria-label="Expand all"
18416
18611
  [disabled]="rowList().length === 0"
18417
18612
  (click)="expandAllRows()"
@@ -18425,7 +18620,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18425
18620
  <th
18426
18621
  scope="col"
18427
18622
  [class]="headerCellClass(col)"
18428
- [style.width]="col.width ?? null"
18623
+ [style.width]="col.width ?? col.maxWidth ?? null"
18624
+ [style.max-width]="col.maxWidth ?? null"
18429
18625
  >
18430
18626
  @if (headerTemplateFor(col.key); as headerTpl) {
18431
18627
  <ng-container
@@ -18434,7 +18630,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18434
18630
  } @else if (col.sortable) {
18435
18631
  <button
18436
18632
  type="button"
18437
- class="inline-flex cursor-pointer items-center gap-1 rounded-sm font-medium text-neutral-600 hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:text-white"
18633
+ class="inline-flex cursor-pointer items-center gap-1 rounded-sm font-medium text-neutral-600 hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:text-white"
18438
18634
  (click)="onSortHeaderClick(col.key)"
18439
18635
  >
18440
18636
  <span>{{ col.header }}</span>
@@ -18456,14 +18652,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18456
18652
  @switch (bodyKind()) {
18457
18653
  @case ('loading') {
18458
18654
  <tr>
18459
- <td class="px-3 py-6" [attr.colspan]="colSpan()">
18655
+ <td class="px-[1.125rem] py-6" [attr.colspan]="colSpan()">
18460
18656
  <africanies-loading-state [message]="loadingLabel()" />
18461
18657
  </td>
18462
18658
  </tr>
18463
18659
  }
18464
18660
  @case ('error') {
18465
18661
  <tr>
18466
- <td class="px-3 py-6" [attr.colspan]="colSpan()">
18662
+ <td class="px-[1.125rem] py-6" [attr.colspan]="colSpan()">
18467
18663
  <africanies-error-state
18468
18664
  [message]="errorMessage()"
18469
18665
  [refreshing]="refreshing()"
@@ -18474,7 +18670,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18474
18670
  }
18475
18671
  @case ('empty') {
18476
18672
  <tr>
18477
- <td class="px-3 py-6" [attr.colspan]="colSpan()">
18673
+ <td class="px-[1.125rem] py-6" [attr.colspan]="colSpan()">
18478
18674
  <africanies-empty-state
18479
18675
  [message]="emptyMessage()"
18480
18676
  [refreshing]="refreshing()"
@@ -18490,7 +18686,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18490
18686
  <td [class]="expandBodyClass(i)">
18491
18687
  <button
18492
18688
  type="button"
18493
- class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-background-welcome hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18689
+ class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
18494
18690
  [attr.aria-label]="
18495
18691
  isRowExpanded(row, i)
18496
18692
  ? 'Collapse row details'
@@ -18509,7 +18705,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18509
18705
  @for (col of columns(); track col.key) {
18510
18706
  <td
18511
18707
  [class]="bodyCellClass(col, i)"
18512
- [style.width]="col.width ?? null"
18708
+ [style.width]="col.width ?? col.maxWidth ?? null"
18709
+ [style.max-width]="col.maxWidth ?? null"
18513
18710
  >
18514
18711
  @if (templateFor(col.key); as tpl) {
18515
18712
  <ng-container
@@ -18539,17 +18736,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18539
18736
  [class.pointer-events-none]="!isRowExpanded(row, i)"
18540
18737
  >
18541
18738
  <div
18542
- class="min-h-0 overflow-hidden border-t border-border/70 bg-neutral-50 dark:border-white/10 dark:bg-white/[0.05]"
18739
+ class="min-h-0 overflow-hidden border-t border-border-strong bg-surface-sunken dark:border-white/10 dark:bg-white/[0.05]"
18543
18740
  >
18544
18741
  <dl
18545
- class="m-0 grid gap-x-6 gap-y-3 px-3 py-3 transition-opacity duration-200 ease-out motion-reduce:transition-none sm:grid-cols-2 lg:grid-cols-3"
18742
+ class="m-0 grid gap-x-6 gap-y-3 px-[1.125rem] py-4 transition-opacity duration-200 ease-out motion-reduce:transition-none sm:grid-cols-2 lg:grid-cols-3"
18546
18743
  [class.opacity-0]="!isRowExpanded(row, i)"
18547
18744
  [class.opacity-100]="isRowExpanded(row, i)"
18548
18745
  >
18549
18746
  @for (detail of rowDetailDefs(); track detail.label()) {
18550
18747
  <div class="min-w-0">
18551
18748
  <dt
18552
- class="m-0 text-caption font-medium text-neutral-500 dark:text-neutral-400"
18749
+ class="m-0 text-caption font-medium text-neutral-600 dark:text-neutral-400"
18553
18750
  >
18554
18751
  {{ detail.label() }}
18555
18752
  </dt>
@@ -18574,6 +18771,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18574
18771
  }
18575
18772
  </tbody>
18576
18773
  </table>
18774
+ @if (paginationLoading()) {
18775
+ <div
18776
+ class="pointer-events-none absolute inset-0 z-[5] flex items-start justify-center bg-surface/70 px-4 pt-16 dark:bg-ink-950/70"
18777
+ data-testid="africanies-table-keep-rows-loading"
18778
+ aria-hidden="true"
18779
+ >
18780
+ <africanies-loading-state
18781
+ mode="inline"
18782
+ [message]="loadingLabel()"
18783
+ />
18784
+ </div>
18785
+ }
18577
18786
  </div>
18578
18787
 
18579
18788
  @if (meta(); as pager) {
@@ -18586,8 +18795,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18586
18795
  />
18587
18796
  }
18588
18797
  </div>
18589
- `, styles: [":host tr.africanies-table-row:hover>td{background-color:#eef2f6}:host-context(.dark) tr.africanies-table-row:hover>td{background-color:#272729}\n"] }]
18590
- }], propDecorators: { expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], rowTrackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowTrackBy", required: false }] }], rowExpandChange: [{ type: i0.Output, args: ["rowExpandChange"] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], showRefresh: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRefresh", required: false }] }], refreshLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "refreshLabel", required: false }] }], refreshing: [{ type: i0.Input, args: [{ isSignal: true, alias: "refreshing", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingLabel", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], showFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFilter", required: false }] }], filterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterLabel", required: false }] }], filterCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterCount", required: false }] }], showExport: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExport", required: false }] }], exportLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportLabel", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], refreshClick: [{ type: i0.Output, args: ["refreshClick"] }], filterClick: [{ type: i0.Output, args: ["filterClick"] }], exportClick: [{ type: i0.Output, args: ["exportClick"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], sizeChange: [{ type: i0.Output, args: ["sizeChange"] }], cellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CellDefDirective), { isSignal: true }] }], headerCellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => HeaderCellDefDirective), { isSignal: true }] }], rowDetailDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => RowDetailDefDirective), { isSignal: true }] }] } });
18798
+ `, styles: [":host tr.africanies-table-row:hover>td{background-color:#e9eef5}:host-context(.dark) tr.africanies-table-row:hover>td{background-color:#272729}\n"] }]
18799
+ }], propDecorators: { expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], rowTrackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowTrackBy", required: false }] }], rowExpandChange: [{ type: i0.Output, args: ["rowExpandChange"] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], stickyHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyHeader", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], showRefresh: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRefresh", required: false }] }], refreshLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "refreshLabel", required: false }] }], refreshing: [{ type: i0.Input, args: [{ isSignal: true, alias: "refreshing", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingLabel", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], showFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFilter", required: false }] }], filterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterLabel", required: false }] }], filterCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterCount", required: false }] }], showExport: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExport", required: false }] }], exportLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportLabel", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], refreshClick: [{ type: i0.Output, args: ["refreshClick"] }], filterClick: [{ type: i0.Output, args: ["filterClick"] }], exportClick: [{ type: i0.Output, args: ["exportClick"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], sizeChange: [{ type: i0.Output, args: ["sizeChange"] }], cellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => CellDefDirective), { isSignal: true }] }], headerCellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => HeaderCellDefDirective), { isSignal: true }] }], rowDetailDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => RowDetailDefDirective), { isSignal: true }] }] } });
18591
18800
 
18592
18801
  /**
18593
18802
  * Presentational table with template-based cells.
@@ -18733,7 +18942,7 @@ class StepperComponent {
18733
18942
  if (past) {
18734
18943
  return 'cursor-pointer text-ink hover:bg-background-welcome dark:text-white dark:hover:bg-white/10';
18735
18944
  }
18736
- return 'opacity-40 text-neutral-500 dark:text-neutral-400';
18945
+ return 'opacity-40 text-neutral-600 dark:text-neutral-400';
18737
18946
  }
18738
18947
  stepBadgeClass(index) {
18739
18948
  const base = 'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full border text-body-sm font-medium';
@@ -18741,7 +18950,7 @@ class StepperComponent {
18741
18950
  const c = this.modeColor.classes();
18742
18951
  return `${base} ${c.border} ${c.bg} text-white`;
18743
18952
  }
18744
- return `${base} border-border bg-white text-neutral-500 dark:border-white/20 dark:bg-ink-950 dark:text-neutral-400`;
18953
+ return `${base} border-control bg-surface text-neutral-600 dark:border-white/20 dark:bg-ink-950 dark:text-neutral-400`;
18745
18954
  }
18746
18955
  canActivate(index) {
18747
18956
  if (index === this.activeIndex()) {
@@ -18766,7 +18975,7 @@ class StepperComponent {
18766
18975
  <div class="flex flex-col gap-5 text-ink dark:text-white">
18767
18976
  <div class="flex flex-col gap-2">
18768
18977
  <p
18769
- class="m-0 text-caption font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-400"
18978
+ class="m-0 text-caption font-medium uppercase tracking-wide text-neutral-600 dark:text-neutral-400"
18770
18979
  >
18771
18980
  Step {{ activeIndex() + 1 }} of {{ steps().length }}
18772
18981
  <span
@@ -18795,7 +19004,7 @@ class StepperComponent {
18795
19004
  <li class="flex min-w-0 shrink-0 items-center gap-1">
18796
19005
  <button
18797
19006
  type="button"
18798
- class="inline-flex max-w-38 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink disabled:cursor-not-allowed"
19007
+ class="inline-flex max-w-38 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ink disabled:cursor-not-allowed"
18799
19008
  [class]="headerButtonClass(i)"
18800
19009
  [attr.aria-current]="i === activeIndex() ? 'step' : null"
18801
19010
  [disabled]="!canActivate(i)"
@@ -18834,7 +19043,7 @@ class StepperComponent {
18834
19043
  }
18835
19044
  </div>
18836
19045
  </div>
18837
- `, isInline: true, styles: [".africanies-step-rail{scrollbar-width:thin}.africanies-step-body{min-width:0;overflow-x:clip;padding-inline:2px;margin-inline:-2px}.africanies-step-pane{min-width:0;will-change:opacity,transform}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19046
+ `, isInline: true, styles: [".africanies-step-rail{scrollbar-width:thin}.africanies-step-body{min-width:0;overflow-x:clip;padding-inline:4px;margin-inline:-4px}.africanies-step-pane{min-width:0;will-change:opacity,transform}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18838
19047
  }
18839
19048
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StepperComponent, decorators: [{
18840
19049
  type: Component,
@@ -18842,7 +19051,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18842
19051
  <div class="flex flex-col gap-5 text-ink dark:text-white">
18843
19052
  <div class="flex flex-col gap-2">
18844
19053
  <p
18845
- class="m-0 text-caption font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-400"
19054
+ class="m-0 text-caption font-medium uppercase tracking-wide text-neutral-600 dark:text-neutral-400"
18846
19055
  >
18847
19056
  Step {{ activeIndex() + 1 }} of {{ steps().length }}
18848
19057
  <span
@@ -18871,7 +19080,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18871
19080
  <li class="flex min-w-0 shrink-0 items-center gap-1">
18872
19081
  <button
18873
19082
  type="button"
18874
- class="inline-flex max-w-38 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink disabled:cursor-not-allowed"
19083
+ class="inline-flex max-w-38 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ink disabled:cursor-not-allowed"
18875
19084
  [class]="headerButtonClass(i)"
18876
19085
  [attr.aria-current]="i === activeIndex() ? 'step' : null"
18877
19086
  [disabled]="!canActivate(i)"
@@ -18910,7 +19119,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18910
19119
  }
18911
19120
  </div>
18912
19121
  </div>
18913
- `, styles: [".africanies-step-rail{scrollbar-width:thin}.africanies-step-body{min-width:0;overflow-x:clip;padding-inline:2px;margin-inline:-2px}.africanies-step-pane{min-width:0;will-change:opacity,transform}\n"] }]
19122
+ `, styles: [".africanies-step-rail{scrollbar-width:thin}.africanies-step-body{min-width:0;overflow-x:clip;padding-inline:4px;margin-inline:-4px}.africanies-step-pane{min-width:0;will-change:opacity,transform}\n"] }]
18914
19123
  }], ctorParameters: () => [], propDecorators: { steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: true }] }], activeIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIndex", required: true }] }], linear: [{ type: i0.Input, args: [{ isSignal: true, alias: "linear", required: false }] }], animateSteps: [{ type: i0.Input, args: [{ isSignal: true, alias: "animateSteps", required: false }] }], activeIndexChange: [{ type: i0.Output, args: ["activeIndexChange"] }], stepDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StepDefDirective), { isSignal: true }] }], stepPane: [{ type: i0.ViewChild, args: ['stepPane', { isSignal: true }] }] } });
18915
19124
 
18916
19125
  /**