@africanies/angular-web-sdk 0.1.10 → 0.1.12

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 (98) hide show
  1. package/.nx/cache/{14995846848874973576 → 14892813664845064572}/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +1634 -1016
  2. package/.nx/cache/14892813664845064572/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  3. package/.nx/cache/{14995846848874973576 → 14892813664845064572}/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +261 -137
  4. package/.nx/cache/{14876618240301288353 → 7821397806208783174}/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +2641 -1011
  5. package/.nx/cache/7821397806208783174/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  6. package/.nx/cache/{14876618240301288353 → 7821397806208783174}/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +460 -147
  7. package/.nx/cache/run.json +16 -16
  8. package/.nx/cache/terminalOutputs/{14876618240301288353 → 14892813664845064572} +2 -2
  9. package/.nx/cache/terminalOutputs/16208231681037145050 +85 -0
  10. package/.nx/cache/terminalOutputs/{1446890025183346611 → 17909810599991457481} +15 -7
  11. package/.nx/cache/terminalOutputs/{7978448102466861029 → 2927637324428060203} +41 -31
  12. package/.nx/cache/terminalOutputs/{17394769338046050914 → 4787481949164239892} +2 -2
  13. package/.nx/cache/terminalOutputs/{14995846848874973576 → 7821397806208783174} +2 -2
  14. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db +0 -0
  15. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-shm +0 -0
  16. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-wal +0 -0
  17. package/.nx/workspace-data/d/daemon.log +3694 -0
  18. package/.nx/workspace-data/d/server-process.json +2 -2
  19. package/.nx/workspace-data/file-map.json +1508 -1478
  20. package/.nx/workspace-data/nx_files.nxt +0 -0
  21. package/.nx/workspace-data/project-graph.json +2 -2
  22. package/README.md +0 -17
  23. package/apps/playground/src/app/pages/forms-page.ts +70 -14
  24. package/apps/playground/src/app/snippets/forms.snippets.ts +41 -6
  25. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +1149 -142
  26. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -1
  27. package/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +205 -18
  28. package/libs/africanies-ui/src/index.ts +18 -0
  29. package/libs/africanies-ui/src/lib/forms/index.ts +20 -0
  30. package/libs/africanies-ui/src/lib/forms/phone-input/index.ts +21 -0
  31. package/libs/africanies-ui/src/lib/forms/phone-input/phone-codes.ts +264 -0
  32. package/libs/africanies-ui/src/lib/forms/phone-input/phone-input.component.ts +477 -0
  33. package/libs/africanies-ui/src/lib/forms/phone-input/phone-input.types.ts +35 -0
  34. package/libs/africanies-ui/src/lib/forms/phone-input/phone-input.utils.spec.ts +81 -0
  35. package/libs/africanies-ui/src/lib/forms/phone-input/phone-input.utils.ts +275 -0
  36. package/libs/africanies-ui/src/lib/modules/africanies-forms.module.ts +2 -0
  37. package/libs/africanies-ui/src/lib/navigation/segment/segment.component.ts +2 -1
  38. package/libs/africanies-ui/src/lib/navigation/shipping-mode-switch/shipping-mode-switch.component.spec.ts +15 -0
  39. package/libs/africanies-ui/src/lib/navigation/shipping-mode-switch/shipping-mode-switch.component.ts +6 -3
  40. package/libs/africanies-ui/src/lib/table/table.component.ts +70 -65
  41. package/package.json +1 -1
  42. package/.nx/cache/13466509747676776487/dist/libs/africanies-core/README.md +0 -85
  43. package/.nx/cache/13466509747676776487/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +0 -4634
  44. package/.nx/cache/13466509747676776487/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +0 -1
  45. package/.nx/cache/13466509747676776487/dist/libs/africanies-core/package.json +0 -31
  46. package/.nx/cache/13466509747676776487/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +0 -2743
  47. package/.nx/cache/14416694322692463866/dist/libs/africanies-models/README.md +0 -60
  48. package/.nx/cache/14416694322692463866/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs +0 -599
  49. package/.nx/cache/14416694322692463866/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs.map +0 -1
  50. package/.nx/cache/14416694322692463866/dist/libs/africanies-models/package.json +0 -23
  51. package/.nx/cache/14416694322692463866/dist/libs/africanies-models/types/africanies-africanies-models.d.ts +0 -1452
  52. package/.nx/cache/14876618240301288353/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +0 -1
  53. package/.nx/cache/14995846848874973576/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +0 -1
  54. package/.nx/cache/17626862048742598274/dist/libs/africanies-theme/README.md +0 -46
  55. package/.nx/cache/17626862048742598274/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +0 -221
  56. package/.nx/cache/17626862048742598274/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +0 -1
  57. package/.nx/cache/17626862048742598274/dist/libs/africanies-theme/package.json +0 -35
  58. package/.nx/cache/17626862048742598274/dist/libs/africanies-theme/tailwind-preset.cjs +0 -253
  59. package/.nx/cache/17626862048742598274/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +0 -166
  60. package/.nx/cache/6664915487114632770/dist/libs/africanies-icons/README.md +0 -69
  61. package/.nx/cache/6664915487114632770/dist/libs/africanies-icons/assets/icons.sprite.svg +0 -1844
  62. package/.nx/cache/6664915487114632770/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs +0 -832
  63. package/.nx/cache/6664915487114632770/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs.map +0 -1
  64. package/.nx/cache/6664915487114632770/dist/libs/africanies-icons/package.json +0 -30
  65. package/.nx/cache/6664915487114632770/dist/libs/africanies-icons/types/africanies-africanies-icons.d.ts +0 -108
  66. package/.nx/cache/6673686859704879504/dist/libs/africanies-theme/README.md +0 -46
  67. package/.nx/cache/6673686859704879504/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +0 -231
  68. package/.nx/cache/6673686859704879504/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +0 -1
  69. package/.nx/cache/6673686859704879504/dist/libs/africanies-theme/package.json +0 -35
  70. package/.nx/cache/6673686859704879504/dist/libs/africanies-theme/tailwind-preset.cjs +0 -262
  71. package/.nx/cache/6673686859704879504/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +0 -174
  72. package/.nx/cache/9040451482998546630/dist/libs/africanies-core/README.md +0 -85
  73. package/.nx/cache/9040451482998546630/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +0 -4511
  74. package/.nx/cache/9040451482998546630/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +0 -1
  75. package/.nx/cache/9040451482998546630/dist/libs/africanies-core/package.json +0 -31
  76. package/.nx/cache/9040451482998546630/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +0 -2741
  77. package/.nx/cache/terminalOutputs/13466509747676776487 +0 -21
  78. package/.nx/cache/terminalOutputs/13555699813138069787 +0 -6
  79. package/.nx/cache/terminalOutputs/14061940909886036014 +0 -173
  80. package/.nx/cache/terminalOutputs/14416694322692463866 +0 -108
  81. package/.nx/cache/terminalOutputs/17626862048742598274 +0 -21
  82. package/.nx/cache/terminalOutputs/6337357866566791975 +0 -6
  83. package/.nx/cache/terminalOutputs/6664915487114632770 +0 -44
  84. package/.nx/cache/terminalOutputs/6673686859704879504 +0 -21
  85. package/.nx/cache/terminalOutputs/783228502946611950 +0 -19
  86. package/.nx/cache/terminalOutputs/9040451482998546630 +0 -21
  87. /package/.nx/cache/{14876618240301288353 → 14892813664845064572}/dist/libs/africanies-ui/README.md +0 -0
  88. /package/.nx/cache/{14876618240301288353 → 14892813664845064572}/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  89. /package/.nx/cache/{14876618240301288353 → 14892813664845064572}/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  90. /package/.nx/cache/{14876618240301288353 → 14892813664845064572}/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +0 -0
  91. /package/.nx/cache/{14876618240301288353 → 14892813664845064572}/dist/libs/africanies-ui/assets/carriers/dhl.svg +0 -0
  92. /package/.nx/cache/{14876618240301288353 → 14892813664845064572}/dist/libs/africanies-ui/package.json +0 -0
  93. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/README.md +0 -0
  94. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  95. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  96. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +0 -0
  97. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/assets/carriers/dhl.svg +0 -0
  98. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/package.json +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',
@@ -112,15 +112,15 @@ class ButtonComponent {
112
112
  }
113
113
  else if (variant === 'flat') {
114
114
  variantClass =
115
- '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';
116
116
  }
117
117
  else if (variant === 'ghost') {
118
118
  variantClass =
119
- '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';
120
120
  }
121
121
  else if (variant === 'ghost-danger') {
122
122
  variantClass =
123
- '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';
124
124
  }
125
125
  else if (variant === 'underline') {
126
126
  variantClass =
@@ -128,7 +128,7 @@ class ButtonComponent {
128
128
  }
129
129
  else {
130
130
  variantClass =
131
- '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';
132
132
  }
133
133
  return `${base} ${sizes[this.size()]} ${variantClass}`;
134
134
  }, /* @ts-ignore */
@@ -271,8 +271,8 @@ class ToastItemComponent {
271
271
  return v === 'warning' || v === 'danger' ? 'assertive' : 'polite';
272
272
  }, /* @ts-ignore */
273
273
  ...(ngDevMode ? [{ debugName: "a11yLive" }] : /* istanbul ignore next */ []));
274
- shellClass = computed(() => 'africanies-toast-enter relative flex w-full overflow-hidden rounded-lg border border-border ' +
275
- 'bg-white/95 text-ink shadow-lg backdrop-blur-md ' +
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
276
  'dark:border-white/15 dark:bg-ink-950/95 dark:text-white', /* @ts-ignore */
277
277
  ...(ngDevMode ? [{ debugName: "shellClass" }] : /* istanbul ignore next */ []));
278
278
  accentClass = computed(() => `w-1 shrink-0 self-stretch ${accentFill(this.item().variant)}`, /* @ts-ignore */
@@ -280,11 +280,11 @@ class ToastItemComponent {
280
280
  iconClass = computed(() => {
281
281
  switch (this.item().variant) {
282
282
  case 'success':
283
- return 'text-export dark:text-export-light';
283
+ return 'text-export-strong dark:text-export-light';
284
284
  case 'warning':
285
285
  return 'text-warning-dark dark:text-warning';
286
286
  case 'danger':
287
- return 'text-danger';
287
+ return 'text-danger-dark dark:text-danger';
288
288
  default:
289
289
  return 'text-ink-blue dark:text-neutral-300';
290
290
  }
@@ -294,11 +294,11 @@ class ToastItemComponent {
294
294
  const base = 'm-0 break-words text-body-sm font-semibold';
295
295
  switch (this.item().variant) {
296
296
  case 'success':
297
- return `${base} text-export dark:text-export-light`;
297
+ return `${base} text-export-strong dark:text-export-light`;
298
298
  case 'warning':
299
299
  return `${base} text-warning-dark dark:text-warning`;
300
300
  case 'danger':
301
- return `${base} text-danger`;
301
+ return `${base} text-danger-dark dark:text-danger`;
302
302
  default:
303
303
  return `${base} text-ink dark:text-white`;
304
304
  }
@@ -1504,9 +1504,9 @@ class ActionMenuComponent {
1504
1504
  const active = this.activeIndex() === index;
1505
1505
  const base = 'flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-body-sm no-underline transition-colors ' +
1506
1506
  'disabled:cursor-not-allowed disabled:opacity-50 ' +
1507
- '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';
1508
1508
  if (item.danger) {
1509
- return (`${base} text-danger ` +
1509
+ return (`${base} text-danger-dark dark:text-danger ` +
1510
1510
  (active
1511
1511
  ? 'bg-danger-subtle dark:bg-danger/15'
1512
1512
  : 'hover:bg-danger-subtle dark:hover:bg-danger/15'));
@@ -1710,7 +1710,7 @@ class ActionMenuComponent {
1710
1710
  >
1711
1711
  <div
1712
1712
  #panel
1713
- 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"
1714
1714
  role="menu"
1715
1715
  tabindex="-1"
1716
1716
  [attr.aria-label]="ariaLabel()"
@@ -1827,7 +1827,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1827
1827
  >
1828
1828
  <div
1829
1829
  #panel
1830
- 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"
1831
1831
  role="menu"
1832
1832
  tabindex="-1"
1833
1833
  [attr.aria-label]="ariaLabel()"
@@ -2056,10 +2056,10 @@ class ErrorIndicatorComponent {
2056
2056
  aria-hidden="true"
2057
2057
  >
2058
2058
  <span
2059
- 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"
2060
2060
  ></span>
2061
2061
  <span
2062
- 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"
2063
2063
  ></span>
2064
2064
  </span>
2065
2065
 
@@ -2074,7 +2074,7 @@ class ErrorIndicatorComponent {
2074
2074
  type="button"
2075
2075
  variant="ghost"
2076
2076
  size="sm"
2077
- 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"
2078
2078
  [class.opacity-50]="disabled()"
2079
2079
  [disabled]="disabled()"
2080
2080
  (click)="onRetry()"
@@ -2101,10 +2101,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2101
2101
  aria-hidden="true"
2102
2102
  >
2103
2103
  <span
2104
- 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"
2105
2105
  ></span>
2106
2106
  <span
2107
- 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"
2108
2108
  ></span>
2109
2109
  </span>
2110
2110
 
@@ -2119,7 +2119,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2119
2119
  type="button"
2120
2120
  variant="ghost"
2121
2121
  size="sm"
2122
- 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"
2123
2123
  [class.opacity-50]="disabled()"
2124
2124
  [disabled]="disabled()"
2125
2125
  (click)="onRetry()"
@@ -2170,8 +2170,8 @@ class ErrorStateComponent {
2170
2170
  class="flex flex-col items-center justify-center gap-3 py-10 px-4 text-center text-ink dark:text-white"
2171
2171
  role="alert"
2172
2172
  >
2173
- <africanies-icon name="warning" [size]="32" class="text-danger" />
2174
- <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>
2175
2175
  <button
2176
2176
  africanies-button
2177
2177
  type="button"
@@ -2201,8 +2201,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2201
2201
  class="flex flex-col items-center justify-center gap-3 py-10 px-4 text-center text-ink dark:text-white"
2202
2202
  role="alert"
2203
2203
  >
2204
- <africanies-icon name="warning" [size]="32" class="text-danger" />
2205
- <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>
2206
2206
  <button
2207
2207
  africanies-button
2208
2208
  type="button"
@@ -2739,11 +2739,11 @@ class AlertComponent {
2739
2739
  iconClass = computed(() => {
2740
2740
  switch (this.variant()) {
2741
2741
  case 'success':
2742
- return 'text-export dark:text-export-light';
2742
+ return 'text-export-strong dark:text-export-light';
2743
2743
  case 'warning':
2744
2744
  return 'text-warning-dark dark:text-warning';
2745
2745
  case 'danger':
2746
- return 'text-danger';
2746
+ return 'text-danger-dark dark:text-danger';
2747
2747
  default:
2748
2748
  return 'text-ink-blue dark:text-neutral-300';
2749
2749
  }
@@ -2753,11 +2753,11 @@ class AlertComponent {
2753
2753
  const base = 'm-0 text-body font-medium';
2754
2754
  switch (this.variant()) {
2755
2755
  case 'success':
2756
- return `${base} text-export dark:text-export-light`;
2756
+ return `${base} text-export-strong dark:text-export-light`;
2757
2757
  case 'warning':
2758
2758
  return `${base} text-warning-dark dark:text-warning`;
2759
2759
  case 'danger':
2760
- return `${base} text-danger`;
2760
+ return `${base} text-danger-dark dark:text-danger`;
2761
2761
  default:
2762
2762
  return `${base} text-ink dark:text-white`;
2763
2763
  }
@@ -2860,19 +2860,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2860
2860
  * Compact status / category label.
2861
2861
  *
2862
2862
  * Soft fills stay readable in dark mode (translucent accent tints, not
2863
- * light-only subtle backgrounds).
2863
+ * light-only subtle backgrounds). Pass {@link color} for a custom accent —
2864
+ * text uses that color and the background is a faded mix of the same.
2864
2865
  *
2865
2866
  * @example
2866
2867
  * ```html
2867
2868
  * <africanies-chip variant="success">Delivered</africanies-chip>
2868
- * <africanies-chip variant="import" icon="truck">In transit</africanies-chip>
2869
+ * <africanies-chip variant="violet">Priority</africanies-chip>
2870
+ * <africanies-chip color="#0f766e">Pay Later</africanies-chip>
2869
2871
  * <africanies-chip variant="warning" [removable]="true" (removed)="clear()">Pending</africanies-chip>
2870
2872
  * ```
2871
2873
  */
2872
2874
  class ChipComponent {
2873
- /** Semantic tone. Defaults to `neutral`. */
2875
+ /** Semantic tone. Defaults to `neutral`. Ignored when {@link color} is set. */
2874
2876
  variant = input('neutral', /* @ts-ignore */
2875
2877
  ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
2878
+ /**
2879
+ * Optional CSS color (`#7c3aed`, `rgb(…)`, `oklch(…)`, …). When set, overrides
2880
+ * {@link variant}: label uses the color and the fill is a faded mix of it.
2881
+ */
2882
+ color = input(null, /* @ts-ignore */
2883
+ ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
2876
2884
  /** Padding / type scale. Defaults to `sm` (table / dense UI). */
2877
2885
  size = input('sm', /* @ts-ignore */
2878
2886
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
@@ -2891,6 +2899,12 @@ class ChipComponent {
2891
2899
  removed = output();
2892
2900
  iconSize = computed(() => (this.size() === 'md' ? 14 : 12), /* @ts-ignore */
2893
2901
  ...(ngDevMode ? [{ debugName: "iconSize" }] : /* istanbul ignore next */ []));
2902
+ /** Trimmed custom accent, or `null` when using a named {@link variant}. */
2903
+ customAccent = computed(() => {
2904
+ const value = this.color()?.trim() ?? '';
2905
+ return value.length > 0 ? value : null;
2906
+ }, /* @ts-ignore */
2907
+ ...(ngDevMode ? [{ debugName: "customAccent" }] : /* istanbul ignore next */ []));
2894
2908
  hostClass = computed(() => {
2895
2909
  const base = 'inline-flex max-w-full items-center gap-1 rounded-md font-sans font-medium ' +
2896
2910
  'border border-transparent';
@@ -2902,21 +2916,35 @@ class ChipComponent {
2902
2916
  }, /* @ts-ignore */
2903
2917
  ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
2904
2918
  /**
2905
- * Soft background + text per variant. Dark mode uses translucent accents so
2906
- * chips never wash out as near-white blobs.
2907
- * @returns Tailwind class string for the active variant.
2919
+ * Soft background + text per variant, or the custom-color host class.
2920
+ * Dark mode uses translucent accents so chips never wash out as near-white
2921
+ * blobs.
2922
+ * @returns Tailwind / host class string for the active tone.
2908
2923
  */
2909
2924
  toneClass() {
2925
+ if (this.customAccent()) {
2926
+ return 'africanies-chip--custom';
2927
+ }
2910
2928
  switch (this.variant()) {
2911
2929
  case 'success':
2912
2930
  case 'export':
2913
- return 'bg-export-subtle text-export dark:bg-export/15 dark:text-export-light';
2931
+ return 'bg-export-subtle text-export-strong dark:bg-export/15 dark:text-export-light';
2914
2932
  case 'import':
2915
- return 'bg-import-subtle text-import dark:bg-import/15 dark:text-import-light';
2933
+ return 'bg-import-subtle text-import-strong dark:bg-import/15 dark:text-import-light';
2916
2934
  case 'warning':
2917
2935
  return 'bg-warning-subtle text-warning-dark dark:bg-warning/15 dark:text-warning';
2918
2936
  case 'danger':
2919
- return 'bg-danger-subtle text-danger dark:bg-danger/15 dark:text-danger';
2937
+ return 'bg-danger-subtle text-danger-dark dark:bg-danger/15 dark:text-danger';
2938
+ case 'info':
2939
+ return 'bg-info-subtle text-info dark:bg-info/20 dark:text-[#93c5fd]';
2940
+ case 'violet':
2941
+ return 'bg-violet-100 text-violet-800 dark:bg-violet-400/15 dark:text-violet-300';
2942
+ case 'teal':
2943
+ return 'bg-teal-100 text-teal-800 dark:bg-teal-400/15 dark:text-teal-300';
2944
+ case 'rose':
2945
+ return 'bg-rose-100 text-rose-800 dark:bg-rose-400/15 dark:text-rose-300';
2946
+ case 'cyan':
2947
+ return 'bg-cyan-100 text-cyan-800 dark:bg-cyan-400/15 dark:text-cyan-300';
2920
2948
  default:
2921
2949
  return 'bg-border/60 text-ink dark:bg-white/10 dark:text-neutral-200';
2922
2950
  }
@@ -2929,7 +2957,7 @@ class ChipComponent {
2929
2957
  this.removed.emit();
2930
2958
  }
2931
2959
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2932
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ChipComponent, isStandalone: true, selector: "africanies-chip", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed" }, host: { attributes: { "role": "status" }, properties: { "class": "hostClass()" } }, ngImport: i0, template: `
2960
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ChipComponent, isStandalone: true, selector: "africanies-chip", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removed: "removed" }, host: { attributes: { "role": "status" }, properties: { "class": "hostClass()", "style.--africanies-chip-accent": "customAccent()" } }, ngImport: i0, template: `
2933
2961
  @if (icon(); as iconName) {
2934
2962
  <africanies-icon [name]="iconName" [size]="iconSize()" class="shrink-0" />
2935
2963
  }
@@ -2937,27 +2965,22 @@ class ChipComponent {
2937
2965
  @if (removable()) {
2938
2966
  <button
2939
2967
  type="button"
2940
- 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"
2941
2969
  [attr.aria-label]="removeLabel()"
2942
2970
  (click)="onRemove($event)"
2943
2971
  >
2944
2972
  <africanies-icon name="close" [size]="12" />
2945
2973
  </button>
2946
2974
  }
2947
- `, isInline: true, dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2975
+ `, isInline: true, styles: [":host.africanies-chip--custom{color:var(--africanies-chip-accent);background-color:color-mix(in srgb,var(--africanies-chip-accent) 14%,transparent);border-color:color-mix(in srgb,var(--africanies-chip-accent) 28%,transparent)}:host-context(.dark):host.africanies-chip--custom{color:color-mix(in srgb,var(--africanies-chip-accent) 68%,white);background-color:color-mix(in srgb,var(--africanies-chip-accent) 22%,transparent);border-color:color-mix(in srgb,var(--africanies-chip-accent) 40%,transparent)}\n"], dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2948
2976
  }
2949
2977
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ChipComponent, decorators: [{
2950
2978
  type: Component,
2951
- args: [{
2952
- selector: 'africanies-chip',
2953
- standalone: true,
2954
- changeDetection: ChangeDetectionStrategy.OnPush,
2955
- imports: [AfricaniesIconComponent],
2956
- host: {
2979
+ args: [{ selector: 'africanies-chip', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AfricaniesIconComponent], host: {
2957
2980
  '[class]': 'hostClass()',
2981
+ '[style.--africanies-chip-accent]': 'customAccent()',
2958
2982
  role: 'status',
2959
- },
2960
- template: `
2983
+ }, template: `
2961
2984
  @if (icon(); as iconName) {
2962
2985
  <africanies-icon [name]="iconName" [size]="iconSize()" class="shrink-0" />
2963
2986
  }
@@ -2965,16 +2988,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2965
2988
  @if (removable()) {
2966
2989
  <button
2967
2990
  type="button"
2968
- 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"
2991
+ 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"
2969
2992
  [attr.aria-label]="removeLabel()"
2970
2993
  (click)="onRemove($event)"
2971
2994
  >
2972
2995
  <africanies-icon name="close" [size]="12" />
2973
2996
  </button>
2974
2997
  }
2975
- `,
2976
- }]
2977
- }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], removable: [{ type: i0.Input, args: [{ isSignal: true, alias: "removable", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], removed: [{ type: i0.Output, args: ["removed"] }] } });
2998
+ `, styles: [":host.africanies-chip--custom{color:var(--africanies-chip-accent);background-color:color-mix(in srgb,var(--africanies-chip-accent) 14%,transparent);border-color:color-mix(in srgb,var(--africanies-chip-accent) 28%,transparent)}:host-context(.dark):host.africanies-chip--custom{color:color-mix(in srgb,var(--africanies-chip-accent) 68%,white);background-color:color-mix(in srgb,var(--africanies-chip-accent) 22%,transparent);border-color:color-mix(in srgb,var(--africanies-chip-accent) 40%,transparent)}\n"] }]
2999
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], removable: [{ type: i0.Input, args: [{ isSignal: true, alias: "removable", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], removed: [{ type: i0.Output, args: ["removed"] }] } });
2978
3000
 
2979
3001
  /**
2980
3002
  * Compact status / category labels (`africanies-chip`).
@@ -3180,7 +3202,7 @@ class ContentStackComponent {
3180
3202
  </p>
3181
3203
  }
3182
3204
  @if (extraLine()) {
3183
- <p class="m-0 text-caption text-neutral-500 dark:text-neutral-400">
3205
+ <p class="m-0 text-caption text-neutral-600 dark:text-neutral-400">
3184
3206
  {{ extraLine() }}
3185
3207
  </p>
3186
3208
  }
@@ -3209,7 +3231,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
3209
3231
  </p>
3210
3232
  }
3211
3233
  @if (extraLine()) {
3212
- <p class="m-0 text-caption text-neutral-500 dark:text-neutral-400">
3234
+ <p class="m-0 text-caption text-neutral-600 dark:text-neutral-400">
3213
3235
  {{ extraLine() }}
3214
3236
  </p>
3215
3237
  }
@@ -3782,8 +3804,8 @@ class InfoPopoverComponent {
3782
3804
  * Bound via `[class]` so arrow position tracks preferred placement.
3783
3805
  */
3784
3806
  panelArrowClass = computed(() => {
3785
- 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';
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-white before:content-[''] dark:before:border-white/15 dark:before:bg-ink-950";
3807
+ 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';
3808
+ 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";
3787
3809
  switch (this.placement()) {
3788
3810
  case 'bottom':
3789
3811
  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`;
@@ -4083,22 +4105,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4083
4105
  * scanner must see full utility names in this package so they survive purge
4084
4106
  * when consumers scan the published UI bundle.
4085
4107
  */
4108
+ /**
4109
+ * Inset focus ring — stays inside the control border box.
4110
+ *
4111
+ * Prefer this over `outline` + `outline-offset`. Those paint *outside* the box
4112
+ * and are clipped by `overflow: hidden|clip|auto` ancestors (modals, drawers,
4113
+ * steppers, scroll panes). `box-sizing` does not affect outlines.
4114
+ */
4115
+ const FORM_FOCUS_WITHIN_CLASS = 'focus-within:outline-none focus-within:ring-2 focus-within:ring-inset focus-within:ring-focus';
4116
+ /** Same inset ring for host / button-style focus-visible. */
4117
+ const FORM_FOCUS_VISIBLE_CLASS = 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus';
4118
+ /** Peer-driven inset ring (checkbox / radio control chrome). */
4119
+ 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';
4120
+ /** Direct focus (OTP cells and similar). */
4121
+ const FORM_FOCUS_CLASS = 'focus:outline-none focus:ring-2 focus:ring-inset focus:ring-focus';
4086
4122
  /** Label above the control. Hidden when empty so callers can omit a label. */
4087
4123
  const FORM_LABEL_CLASS = 'block text-body-sm font-medium text-ink dark:text-white mb-1.5 empty:hidden';
4088
- /** Default control shell — `h-10` matches md `africanies-button`. */
4089
- const FORM_FIELD_CLASS = 'flex items-center w-full h-10 box-border rounded-md border border-border bg-white dark:bg-ink-950 text-ink dark:text-white dark:border-white/25 ' +
4090
- 'focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ink';
4124
+ /**
4125
+ * Default control shell `h-10` matches md `africanies-button`.
4126
+ *
4127
+ * Focus utilities are inlined (not concatenated) so Tailwind content scanning
4128
+ * always sees the full class names in this string literal.
4129
+ */
4130
+ 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 ' +
4131
+ 'focus-within:outline-none focus-within:ring-2 focus-within:ring-inset focus-within:ring-focus';
4091
4132
  /**
4092
4133
  * Error border — `!` so it wins over shell `border-border` /
4093
4134
  * `dark:border-white/25` (same specificity; those utilities sort later in the
4094
4135
  * generated CSS and otherwise paint over `border-danger`).
4095
4136
  *
4137
+ * Use `danger` (`#ff001c`) for both border and focus ring so field chrome
4138
+ * matches validation copy (not the darker accessible `danger-dark` text token).
4139
+ *
4096
4140
  * Keep these literals static for Tailwind content scanning in consumer apps.
4097
4141
  */
4098
- const FORM_FIELD_ERROR_CLASS = '!border-danger dark:!border-danger focus-within:!outline-danger';
4142
+ const FORM_FIELD_ERROR_CLASS = '!border-danger dark:!border-danger focus-within:!ring-danger focus:!ring-danger';
4099
4143
  /** Native input / textarea inside a shell. */
4100
4144
  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 ' +
4101
- 'placeholder:text-neutral-400 disabled:cursor-not-allowed';
4145
+ 'placeholder:text-neutral-500 disabled:cursor-not-allowed';
4102
4146
  /** Prefix / suffix projection gutters. Hidden when nothing is projected. */
4103
4147
  const FORM_AFFIX_CLASS = 'inline-flex items-center shrink-0 px-2.5 text-neutral-600 dark:text-neutral-400 empty:hidden';
4104
4148
  /**
@@ -4113,7 +4157,7 @@ const FORM_DATE_INNER_CLASS = FORM_CONTROL_INNER_CLASS +
4113
4157
  ' cursor-pointer [&::-webkit-calendar-picker-indicator]:cursor-pointer';
4114
4158
  /** Hint under the control (hidden when error is shown). */
4115
4159
  const FORM_HINT_CLASS = 'mt-1.5 text-caption text-neutral-600 dark:text-neutral-400 m-0';
4116
- /** Field-level validation message (not ErrorStateComponent). */
4160
+ /** Field-level validation message (not ErrorStateComponent). Matches `#ff001c`. */
4117
4161
  const FORM_ERROR_CLASS = 'mt-1.5 text-caption text-danger m-0';
4118
4162
  /** Disabled shell — dimmed with not-allowed cursor over the full field chrome. */
4119
4163
  const FORM_DISABLED_CLASS = 'opacity-50 cursor-not-allowed';
@@ -4344,229 +4388,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4344
4388
  }]
4345
4389
  }], 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 }] }] } });
4346
4390
 
4347
- let nextRadioId = 0;
4348
- /**
4349
- * Radio group following the shared AFRICANIES form field pattern.
4350
- *
4351
- * **Prefix/suffix slots are omitted** — radios are mutually exclusive options
4352
- * with labels; affix projection does not apply.
4353
- *
4354
- * Selected ring and center disc follow {@link ModeColorService}
4355
- * (SFN green / STN orange).
4356
- *
4357
- * @typeParam T - Option value type.
4358
- *
4359
- * @example
4360
- * ```html
4361
- * <africanies-radio
4362
- * label="Shipment mode"
4363
- * [options]="modeOptions"
4364
- * [(value)]="mode"
4365
- * />
4366
- * ```
4367
- */
4368
- class RadioComponent {
4369
- modeColor = inject(ModeColorService);
4370
- groupName = `africanies-radio-${++nextRadioId}`;
4371
- hintId = `${this.groupName}-hint`;
4372
- errorId = `${this.groupName}-error`;
4373
- labelClass = FORM_LABEL_CLASS;
4374
- hintClass = FORM_HINT_CLASS;
4375
- errorClass = FORM_ERROR_CLASS;
4376
- /** Group legend / label. */
4377
- label = input('', /* @ts-ignore */
4378
- ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
4379
- /** Helper copy. Hidden while {@link error} is set. */
4380
- hint = input(undefined, /* @ts-ignore */
4381
- ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
4382
- /** Field-level validation message. Distinct from `ErrorStateComponent`. */
4383
- error = input(null, /* @ts-ignore */
4384
- ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
4385
- /** Options rendered as radios. */
4386
- options = input([], /* @ts-ignore */
4387
- ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
4388
- /** Selected option value (`[(value)]` + CVA). */
4389
- value = model(null, /* @ts-ignore */
4390
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4391
- /** Host disable flag (in addition to CVA). */
4392
- disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled',
4393
- transform: booleanAttribute });
4394
- cvaDisabled = signal(false, /* @ts-ignore */
4395
- ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
4396
- disabled = computed(() => this.disabledInput() || this.cvaDisabled(), /* @ts-ignore */
4397
- ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4398
- describedBy = computed(() => {
4399
- if (this.error()) {
4400
- return this.errorId;
4401
- }
4402
- if (this.hint()) {
4403
- return this.hintId;
4404
- }
4405
- return null;
4406
- }, /* @ts-ignore */
4407
- ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
4408
- onChange = () => undefined;
4409
- onTouched = () => undefined;
4410
- trackOption(opt) {
4411
- return String(opt.value);
4412
- }
4413
- isSelected(opt) {
4414
- return Object.is(this.value(), opt.value);
4415
- }
4416
- markClass(opt) {
4417
- const base = 'pointer-events-none flex size-4 items-center justify-center rounded-full border-2 bg-transparent transition-colors ' +
4418
- 'peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ink';
4419
- const err = this.error() ? ' !border-danger dark:!border-danger' : '';
4420
- if (this.isSelected(opt)) {
4421
- // Ring uses mode accent; fill stays transparent so the center dot reads clearly.
4422
- return `${base} ${this.modeColor.classes().border}${err}`;
4423
- }
4424
- return `${base} border-neutral-400 dark:border-white${err}`;
4425
- }
4426
- /** Selected inner disc — SFN green / STN orange. */
4427
- dotClass = computed(() => `block size-1.5 rounded-full ${this.modeColor.classes().bg}`, /* @ts-ignore */
4428
- ...(ngDevMode ? [{ debugName: "dotClass" }] : /* istanbul ignore next */ []));
4429
- /** @param value - Selected option value or `null`. */
4430
- writeValue(value) {
4431
- this.value.set(value);
4432
- }
4433
- /** @param fn - Selection change callback. */
4434
- registerOnChange(fn) {
4435
- this.onChange = fn;
4436
- }
4437
- /** @param fn - Blur callback. */
4438
- registerOnTouched(fn) {
4439
- this.onTouched = fn;
4440
- }
4441
- /** @param isDisabled - Blocks interaction when true. */
4442
- setDisabledState(isDisabled) {
4443
- this.cvaDisabled.set(isDisabled);
4444
- }
4445
- select(opt) {
4446
- if (this.disabled() || opt.disabled) {
4447
- return;
4448
- }
4449
- this.value.set(opt.value);
4450
- this.onChange(opt.value);
4451
- }
4452
- onBlur() {
4453
- this.onTouched();
4454
- }
4455
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RadioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4456
- 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: [
4457
- {
4458
- provide: NG_VALUE_ACCESSOR,
4459
- useExisting: forwardRef(() => RadioComponent),
4460
- multi: true,
4461
- },
4462
- ], ngImport: i0, template: `
4463
- <fieldset
4464
- class="m-0 p-0 border-0 min-w-0"
4465
- [attr.aria-invalid]="error() ? true : null"
4466
- [attr.aria-describedby]="describedBy()"
4467
- >
4468
- <legend [class]="labelClass">{{ label() }}</legend>
4469
- <div class="flex flex-row flex-wrap gap-x-4 gap-y-2" role="radiogroup">
4470
- @for (opt of options(); track trackOption(opt)) {
4471
- <label
4472
- class="inline-flex items-center gap-2.5 text-body text-ink dark:text-white"
4473
- [class.opacity-50]="disabled() || opt.disabled"
4474
- [class.cursor-pointer]="!(disabled() || opt.disabled)"
4475
- [class.cursor-not-allowed]="disabled() || opt.disabled"
4476
- >
4477
- <span class="relative inline-flex size-4 shrink-0">
4478
- <input
4479
- type="radio"
4480
- class="peer absolute inset-0 z-10 m-0 size-full opacity-0"
4481
- [class.cursor-pointer]="!(disabled() || opt.disabled)"
4482
- [class.cursor-not-allowed]="disabled() || opt.disabled"
4483
- [name]="groupName"
4484
- [checked]="isSelected(opt)"
4485
- [disabled]="disabled() || !!opt.disabled"
4486
- (change)="select(opt)"
4487
- (blur)="onBlur()"
4488
- />
4489
- <span [class]="markClass(opt)" aria-hidden="true">
4490
- @if (isSelected(opt)) {
4491
- <span [class]="dotClass()"></span>
4492
- }
4493
- </span>
4494
- </span>
4495
- <span>{{ opt.label }}</span>
4496
- </label>
4497
- }
4498
- </div>
4499
- @if (error(); as err) {
4500
- <p [id]="errorId" [class]="errorClass" role="alert">{{ err }}</p>
4501
- } @else if (hint(); as h) {
4502
- <p [id]="hintId" [class]="hintClass">{{ h }}</p>
4503
- }
4504
- </fieldset>
4505
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
4506
- }
4507
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RadioComponent, decorators: [{
4508
- type: Component,
4509
- args: [{
4510
- selector: 'africanies-radio',
4511
- standalone: true,
4512
- changeDetection: ChangeDetectionStrategy.OnPush,
4513
- host: {
4514
- class: 'inline-flex max-w-full',
4515
- },
4516
- providers: [
4517
- {
4518
- provide: NG_VALUE_ACCESSOR,
4519
- useExisting: forwardRef(() => RadioComponent),
4520
- multi: true,
4521
- },
4522
- ],
4523
- template: `
4524
- <fieldset
4525
- class="m-0 p-0 border-0 min-w-0"
4526
- [attr.aria-invalid]="error() ? true : null"
4527
- [attr.aria-describedby]="describedBy()"
4528
- >
4529
- <legend [class]="labelClass">{{ label() }}</legend>
4530
- <div class="flex flex-row flex-wrap gap-x-4 gap-y-2" role="radiogroup">
4531
- @for (opt of options(); track trackOption(opt)) {
4532
- <label
4533
- class="inline-flex items-center gap-2.5 text-body text-ink dark:text-white"
4534
- [class.opacity-50]="disabled() || opt.disabled"
4535
- [class.cursor-pointer]="!(disabled() || opt.disabled)"
4536
- [class.cursor-not-allowed]="disabled() || opt.disabled"
4537
- >
4538
- <span class="relative inline-flex size-4 shrink-0">
4539
- <input
4540
- type="radio"
4541
- class="peer absolute inset-0 z-10 m-0 size-full opacity-0"
4542
- [class.cursor-pointer]="!(disabled() || opt.disabled)"
4543
- [class.cursor-not-allowed]="disabled() || opt.disabled"
4544
- [name]="groupName"
4545
- [checked]="isSelected(opt)"
4546
- [disabled]="disabled() || !!opt.disabled"
4547
- (change)="select(opt)"
4548
- (blur)="onBlur()"
4549
- />
4550
- <span [class]="markClass(opt)" aria-hidden="true">
4551
- @if (isSelected(opt)) {
4552
- <span [class]="dotClass()"></span>
4553
- }
4554
- </span>
4555
- </span>
4556
- <span>{{ opt.label }}</span>
4557
- </label>
4558
- }
4559
- </div>
4560
- @if (error(); as err) {
4561
- <p [id]="errorId" [class]="errorClass" role="alert">{{ err }}</p>
4562
- } @else if (hint(); as h) {
4563
- <p [id]="hintId" [class]="hintClass">{{ h }}</p>
4564
- }
4565
- </fieldset>
4566
- `,
4567
- }]
4568
- }], 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 }] }] } });
4569
-
4570
4391
  /**
4571
4392
  * Tailwind panel classes for each {@link ModalSize}.
4572
4393
  *
@@ -4880,12 +4701,12 @@ class ModalService {
4880
4701
  ],
4881
4702
  panelClass: [
4882
4703
  'africanies-modal-panel',
4883
- 'bg-white',
4704
+ 'bg-surface',
4884
4705
  'dark:bg-ink-950',
4885
4706
  'text-ink',
4886
4707
  'dark:text-white',
4887
- 'rounded-lg',
4888
- 'shadow-xl',
4708
+ 'rounded-panel',
4709
+ 'shadow-floating',
4889
4710
  'border',
4890
4711
  'border-border',
4891
4712
  'dark:border-white/15',
@@ -5809,7 +5630,7 @@ class SelectComponent {
5809
5630
  <input
5810
5631
  #searchInput
5811
5632
  type="search"
5812
- 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"
5633
+ 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"
5813
5634
  [value]="searchQuery()"
5814
5635
  placeholder="Search…"
5815
5636
  (input)="onSearchInput($event)"
@@ -6137,7 +5958,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
6137
5958
  <input
6138
5959
  #searchInput
6139
5960
  type="search"
6140
- 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"
5961
+ 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"
6141
5962
  [value]="searchQuery()"
6142
5963
  placeholder="Search…"
6143
5964
  (input)="onSearchInput($event)"
@@ -6804,13 +6625,6 @@ class FilterDrawerPanel {
6804
6625
  const opt = Array.isArray(selected) ? selected[0] : selected;
6805
6626
  this.draft.update((s) => ({ ...s, order: opt?.value }));
6806
6627
  }
6807
- onRadioChange(key, value) {
6808
- if (value == null) {
6809
- this.clearField(key);
6810
- return;
6811
- }
6812
- this.setFieldValue(key, value);
6813
- }
6814
6628
  onSelectChange(key, selected) {
6815
6629
  const opt = Array.isArray(selected) ? selected[0] : selected;
6816
6630
  if (!opt) {
@@ -6951,14 +6765,21 @@ class FilterDrawerPanel {
6951
6765
  value,
6952
6766
  });
6953
6767
  }
6954
- toRadioOptions(options) {
6955
- return (options ?? []).map((o) => ({
6956
- label: o.label,
6957
- value: o.value,
6958
- }));
6959
- }
6960
- onReset() {
6961
- const next = resetFilterState(true, this.draft());
6768
+ /**
6769
+ * Boolean Yes/No chips. Prefer field.options when present; otherwise default
6770
+ * colored Yes/No so every boolean filter stays visually distinct.
6771
+ */
6772
+ booleanOptions(field) {
6773
+ if (field.options?.length) {
6774
+ return field.options;
6775
+ }
6776
+ return [
6777
+ { value: '1', label: 'Yes', color: '#25945c' },
6778
+ { value: '0', label: 'No', color: '#FF001C' },
6779
+ ];
6780
+ }
6781
+ onReset() {
6782
+ const next = resetFilterState(true, this.draft());
6962
6783
  this.draft.set(next);
6963
6784
  this.selectedKeys.set([]);
6964
6785
  }
@@ -7204,12 +7025,37 @@ class FilterDrawerPanel {
7204
7025
  </div>
7205
7026
  }
7206
7027
  @case ('boolean') {
7207
- <africanies-radio
7208
- label=""
7209
- [options]="toRadioOptions(field.options)"
7210
- [value]="draft().values[field.key] ?? null"
7211
- (valueChange)="onRadioChange(field.key, $event)"
7212
- />
7028
+ <div class="flex flex-wrap gap-2">
7029
+ @for (opt of booleanOptions(field); track opt.value) {
7030
+ <button
7031
+ type="button"
7032
+ class="cursor-pointer rounded-md border px-2.5 py-1.5 text-body-sm transition-colors"
7033
+ [style.color]="
7034
+ draft().values[field.key] === opt.value
7035
+ ? opt.color ?? null
7036
+ : null
7037
+ "
7038
+ [style.border-color]="
7039
+ draft().values[field.key] === opt.value
7040
+ ? opt.color ?? null
7041
+ : null
7042
+ "
7043
+ [style.background-color]="
7044
+ draft().values[field.key] === opt.value && opt.color
7045
+ ? opt.color + '1A'
7046
+ : null
7047
+ "
7048
+ [class]="
7049
+ draft().values[field.key] === opt.value
7050
+ ? 'font-medium'
7051
+ : 'border-neutral-300 text-neutral-600 hover:border-neutral-400 dark:border-white/15 dark:text-neutral-300'
7052
+ "
7053
+ (click)="setFieldValue(field.key, opt.value)"
7054
+ >
7055
+ {{ opt.label }}
7056
+ </button>
7057
+ }
7058
+ </div>
7213
7059
  }
7214
7060
  @case ('select') {
7215
7061
  <!-- Label already shown in the section header — avoid double label. -->
@@ -7268,7 +7114,7 @@ class FilterDrawerPanel {
7268
7114
  type="button"
7269
7115
  variant="ghost"
7270
7116
  size="sm"
7271
- 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"
7117
+ 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"
7272
7118
  aria-label="Dismiss"
7273
7119
  (click)="applyError.set(null)"
7274
7120
  >
@@ -7300,7 +7146,7 @@ class FilterDrawerPanel {
7300
7146
  </div>
7301
7147
  </div>
7302
7148
  </div>
7303
- `, 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 });
7149
+ `, 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 });
7304
7150
  }
7305
7151
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FilterDrawerPanel, decorators: [{
7306
7152
  type: Component,
@@ -7315,7 +7161,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7315
7161
  AfricaniesIconComponent,
7316
7162
  ButtonComponent,
7317
7163
  DatePickerComponent,
7318
- RadioComponent,
7319
7164
  SelectComponent,
7320
7165
  TextInputComponent,
7321
7166
  ],
@@ -7473,12 +7318,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7473
7318
  </div>
7474
7319
  }
7475
7320
  @case ('boolean') {
7476
- <africanies-radio
7477
- label=""
7478
- [options]="toRadioOptions(field.options)"
7479
- [value]="draft().values[field.key] ?? null"
7480
- (valueChange)="onRadioChange(field.key, $event)"
7481
- />
7321
+ <div class="flex flex-wrap gap-2">
7322
+ @for (opt of booleanOptions(field); track opt.value) {
7323
+ <button
7324
+ type="button"
7325
+ class="cursor-pointer rounded-md border px-2.5 py-1.5 text-body-sm transition-colors"
7326
+ [style.color]="
7327
+ draft().values[field.key] === opt.value
7328
+ ? opt.color ?? null
7329
+ : null
7330
+ "
7331
+ [style.border-color]="
7332
+ draft().values[field.key] === opt.value
7333
+ ? opt.color ?? null
7334
+ : null
7335
+ "
7336
+ [style.background-color]="
7337
+ draft().values[field.key] === opt.value && opt.color
7338
+ ? opt.color + '1A'
7339
+ : null
7340
+ "
7341
+ [class]="
7342
+ draft().values[field.key] === opt.value
7343
+ ? 'font-medium'
7344
+ : 'border-neutral-300 text-neutral-600 hover:border-neutral-400 dark:border-white/15 dark:text-neutral-300'
7345
+ "
7346
+ (click)="setFieldValue(field.key, opt.value)"
7347
+ >
7348
+ {{ opt.label }}
7349
+ </button>
7350
+ }
7351
+ </div>
7482
7352
  }
7483
7353
  @case ('select') {
7484
7354
  <!-- Label already shown in the section header — avoid double label. -->
@@ -7537,7 +7407,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7537
7407
  type="button"
7538
7408
  variant="ghost"
7539
7409
  size="sm"
7540
- 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"
7410
+ 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"
7541
7411
  aria-label="Dismiss"
7542
7412
  (click)="applyError.set(null)"
7543
7413
  >
@@ -7617,11 +7487,11 @@ class DrawerService {
7617
7487
  ],
7618
7488
  panelClass: [
7619
7489
  'africanies-drawer-panel',
7620
- 'bg-white',
7490
+ 'bg-surface',
7621
7491
  'dark:bg-ink-950',
7622
7492
  'text-ink',
7623
7493
  'dark:text-white',
7624
- 'shadow-xl',
7494
+ 'shadow-floating',
7625
7495
  'border-l',
7626
7496
  'border-border',
7627
7497
  'dark:border-white/15',
@@ -7754,8 +7624,8 @@ class ConfirmDialogComponent {
7754
7624
  iconWellClass = computed(() => {
7755
7625
  const well = 'inline-flex size-14 shrink-0 items-center justify-center rounded-2xl';
7756
7626
  return this.options.danger
7757
- ? `${well} bg-danger-subtle text-danger dark:bg-danger/20 dark:text-danger`
7758
- : `${well} bg-export-subtle text-export dark:bg-export/20 dark:text-export-light`;
7627
+ ? `${well} bg-danger-subtle text-danger-dark dark:bg-danger/20 dark:text-danger`
7628
+ : `${well} bg-export-subtle text-export-strong dark:bg-export/20 dark:text-export-light`;
7759
7629
  }, /* @ts-ignore */
7760
7630
  ...(ngDevMode ? [{ debugName: "iconWellClass" }] : /* istanbul ignore next */ []));
7761
7631
  constructor() {
@@ -8141,7 +8011,7 @@ class OverlayFrameComponent {
8141
8011
  <ng-content select="[africaniesOverlayHeader]" />
8142
8012
  </div>
8143
8013
  <div
8144
- class="africanies-overlay-scroll min-h-0 flex-1 overflow-x-clip overflow-y-auto px-[2px] -mx-[2px]"
8014
+ class="africanies-overlay-scroll min-h-0 flex-1 overflow-x-clip overflow-y-auto px-1 -mx-1"
8145
8015
  >
8146
8016
  <ng-content />
8147
8017
  </div>
@@ -8164,7 +8034,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
8164
8034
  <ng-content select="[africaniesOverlayHeader]" />
8165
8035
  </div>
8166
8036
  <div
8167
- class="africanies-overlay-scroll min-h-0 flex-1 overflow-x-clip overflow-y-auto px-[2px] -mx-[2px]"
8037
+ class="africanies-overlay-scroll min-h-0 flex-1 overflow-x-clip overflow-y-auto px-1 -mx-1"
8168
8038
  >
8169
8039
  <ng-content />
8170
8040
  </div>
@@ -9056,7 +8926,7 @@ class CheckboxComponent {
9056
8926
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
9057
8927
  markClass = computed(() => {
9058
8928
  const base = 'pointer-events-none flex size-4 items-center justify-center rounded border text-white transition-colors ' +
9059
- 'peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ink';
8929
+ FORM_FOCUS_VISIBLE_PEER_CLASS;
9060
8930
  const err = this.error() ? ' !border-danger dark:!border-danger' : '';
9061
8931
  if (this.value()) {
9062
8932
  const c = this.modeColor.classes();
@@ -9731,7 +9601,7 @@ class FilePreviewDialogComponent {
9731
9601
  <div
9732
9602
  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"
9733
9603
  >
9734
- <africanies-icon name="eye-slash" [size]="32" class="text-neutral-500" />
9604
+ <africanies-icon name="eye-slash" [size]="32" class="text-neutral-600" />
9735
9605
  <p class="m-0 text-body font-medium text-ink dark:text-white">
9736
9606
  Preview not available
9737
9607
  </p>
@@ -9801,7 +9671,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
9801
9671
  <div
9802
9672
  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"
9803
9673
  >
9804
- <africanies-icon name="eye-slash" [size]="32" class="text-neutral-500" />
9674
+ <africanies-icon name="eye-slash" [size]="32" class="text-neutral-600" />
9805
9675
  <p class="m-0 text-body font-medium text-ink dark:text-white">
9806
9676
  Preview not available
9807
9677
  </p>
@@ -10494,7 +10364,7 @@ class FileUploadComponent {
10494
10364
  <span> · </span>
10495
10365
  <button
10496
10366
  type="button"
10497
- 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"
10367
+ 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"
10498
10368
  (click)="openPreview(item); $event.stopPropagation()"
10499
10369
  >
10500
10370
  View
@@ -10794,7 +10664,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10794
10664
  <span> · </span>
10795
10665
  <button
10796
10666
  type="button"
10797
- 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"
10667
+ 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"
10798
10668
  (click)="openPreview(item); $event.stopPropagation()"
10799
10669
  >
10800
10670
  View
@@ -11208,7 +11078,7 @@ class OtpInputComponent {
11208
11078
  cellClass = computed(() => {
11209
11079
  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 ' +
11210
11080
  'dark:border-white/25 dark:bg-ink-950 dark:text-white ' +
11211
- 'focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-ink disabled:cursor-not-allowed';
11081
+ `${FORM_FOCUS_CLASS} disabled:cursor-not-allowed`;
11212
11082
  if (this.masked()) {
11213
11083
  classes += ' africanies-otp-cell-masked text-body-lg';
11214
11084
  }
@@ -11617,133 +11487,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
11617
11487
  `, styles: [".africanies-otp-cell-masked{-webkit-text-security:disc;text-security:disc}\n"] }]
11618
11488
  }], 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"] }] } });
11619
11489
 
11490
+ let nextRadioId = 0;
11620
11491
  /**
11621
- * Radio form control exports.
11622
- */
11623
-
11624
- let nextSearchComboboxId = 0;
11625
- const PANEL_POSITIONS = [
11626
- {
11627
- originX: 'start',
11628
- originY: 'bottom',
11629
- overlayX: 'start',
11630
- overlayY: 'top',
11631
- offsetY: 4,
11632
- },
11633
- {
11634
- originX: 'start',
11635
- originY: 'top',
11636
- overlayX: 'start',
11637
- overlayY: 'bottom',
11638
- offsetY: -4,
11639
- },
11640
- ];
11641
- /**
11642
- * Generic type-as-you-search combobox with a debounced async lookup and
11643
- * overlay result list.
11492
+ * Radio group following the shared AFRICANIES form field pattern.
11644
11493
  *
11645
- * Pass {@link search} plus {@link optionLabel} (and optional
11646
- * {@link optionSubtitle}) so the same control works for customers, staff,
11647
- * partners, etc. Emits the selected entity via `[(selected)]` / CVA.
11494
+ * **Prefix/suffix slots are omitted** radios are mutually exclusive options
11495
+ * with labels; affix projection does not apply.
11648
11496
  *
11649
- * @typeParam T - Entity type returned by {@link search} and held in
11650
- * {@link selected}.
11497
+ * Selected ring and center disc follow {@link ModeColorService}
11498
+ * (SFN green / STN orange).
11499
+ *
11500
+ * @typeParam T - Option value type.
11651
11501
  *
11652
11502
  * @example
11653
11503
  * ```html
11654
- * <africanies-search-combobox
11655
- * label="Customer"
11656
- * [search]="searchCustomers"
11657
- * [optionLabel]="customerLabel"
11658
- * [optionSubtitle]="customerSubtitle"
11659
- * [(selected)]="customer"
11504
+ * <africanies-radio
11505
+ * label="Shipment mode"
11506
+ * [options]="modeOptions"
11507
+ * [(value)]="mode"
11660
11508
  * />
11661
11509
  * ```
11662
11510
  */
11663
- class SearchComboboxComponent {
11664
- destroyRef = inject(DestroyRef);
11665
- controlId = `africanies-search-combobox-${++nextSearchComboboxId}`;
11666
- hintId = `${this.controlId}-hint`;
11667
- errorId = `${this.controlId}-error`;
11668
- listboxId = `${this.controlId}-listbox`;
11511
+ class RadioComponent {
11512
+ modeColor = inject(ModeColorService);
11513
+ groupName = `africanies-radio-${++nextRadioId}`;
11514
+ hintId = `${this.groupName}-hint`;
11515
+ errorId = `${this.groupName}-error`;
11669
11516
  labelClass = FORM_LABEL_CLASS;
11670
- affixClass = FORM_AFFIX_CLASS;
11671
- innerClass = FORM_CONTROL_INNER_CLASS;
11672
11517
  hintClass = FORM_HINT_CLASS;
11673
11518
  errorClass = FORM_ERROR_CLASS;
11674
- panelPositions = PANEL_POSITIONS;
11675
- minPanelWidth = 224;
11676
- triggerOrigin = viewChild(CdkOverlayOrigin, /* @ts-ignore */
11677
- ...(ngDevMode ? [{ debugName: "triggerOrigin" }] : /* istanbul ignore next */ []));
11678
- querySubject = new Subject();
11679
- onChange = () => undefined;
11680
- onTouched = () => undefined;
11681
- /** Visible field label. */
11519
+ /** Group legend / label. */
11682
11520
  label = input('', /* @ts-ignore */
11683
11521
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
11684
- /** Helper copy under the field. Hidden while {@link error} is set. */
11522
+ /** Helper copy. Hidden while {@link error} is set. */
11685
11523
  hint = input(undefined, /* @ts-ignore */
11686
11524
  ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
11687
- /** Field-level validation message. */
11525
+ /** Field-level validation message. Distinct from `ErrorStateComponent`. */
11688
11526
  error = input(null, /* @ts-ignore */
11689
11527
  ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
11690
- placeholder = input('', /* @ts-ignore */
11691
- ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
11692
- /** Debounce before calling {@link search} (ms). Default 500. */
11693
- debounceMs = input(500, { ...(ngDevMode ? { debugName: "debounceMs" } : /* istanbul ignore next */ {}), transform: numberAttribute });
11694
- /** Minimum trimmed query length before search runs. Default 2. */
11695
- minQueryLength = input(2, { ...(ngDevMode ? { debugName: "minQueryLength" } : /* istanbul ignore next */ {}), transform: numberAttribute });
11696
- emptyMessage = input('No matches', /* @ts-ignore */
11697
- ...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
11698
- /** Async lookup — parent owns HTTP / filtering. */
11699
- search = input.required(/* @ts-ignore */
11700
- ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
11701
- optionLabel = input.required(/* @ts-ignore */
11702
- ...(ngDevMode ? [{ debugName: "optionLabel" }] : /* istanbul ignore next */ []));
11703
- optionSubtitle = input(undefined, /* @ts-ignore */
11704
- ...(ngDevMode ? [{ debugName: "optionSubtitle" }] : /* istanbul ignore next */ []));
11705
- optionBadge = input(undefined, /* @ts-ignore */
11706
- ...(ngDevMode ? [{ debugName: "optionBadge" }] : /* istanbul ignore next */ []));
11707
- optionMark = input(undefined, /* @ts-ignore */
11708
- ...(ngDevMode ? [{ debugName: "optionMark" }] : /* istanbul ignore next */ []));
11709
- markTooltip = input('Professional Business Plan', /* @ts-ignore */
11710
- ...(ngDevMode ? [{ debugName: "markTooltip" }] : /* istanbul ignore next */ []));
11711
- trackBy = input((item) => this.optionLabel()(item), /* @ts-ignore */
11712
- ...(ngDevMode ? [{ debugName: "trackBy" }] : /* istanbul ignore next */ []));
11713
- selected = model(null, /* @ts-ignore */
11714
- ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
11715
- queryChange = output();
11528
+ /** Options rendered as radios. */
11529
+ options = input([], /* @ts-ignore */
11530
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
11531
+ /** Selected option value (`[(value)]` + CVA). */
11532
+ value = model(null, /* @ts-ignore */
11533
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
11534
+ /** Host disable flag (in addition to CVA). */
11716
11535
  disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled',
11717
11536
  transform: booleanAttribute });
11718
11537
  cvaDisabled = signal(false, /* @ts-ignore */
11719
11538
  ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
11720
11539
  disabled = computed(() => this.disabledInput() || this.cvaDisabled(), /* @ts-ignore */
11721
11540
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
11722
- query = signal('', /* @ts-ignore */
11723
- ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
11724
- open = signal(false, /* @ts-ignore */
11725
- ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
11726
- loading = signal(false, /* @ts-ignore */
11727
- ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
11728
- options = signal([], /* @ts-ignore */
11729
- ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
11730
- searchError = signal(null, /* @ts-ignore */
11731
- ...(ngDevMode ? [{ debugName: "searchError" }] : /* istanbul ignore next */ []));
11732
- activeIndex = signal(-1, /* @ts-ignore */
11733
- ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
11734
- panelWidth = signal(this.minPanelWidth, /* @ts-ignore */
11735
- ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
11736
- shellClass = computed(() => {
11737
- let classes = FORM_FIELD_CLASS;
11738
- if (this.error()) {
11739
- classes += ` ${FORM_FIELD_ERROR_CLASS}`;
11740
- }
11741
- if (this.disabled()) {
11742
- classes += ` ${FORM_DISABLED_CLASS}`;
11743
- }
11744
- return classes;
11745
- }, /* @ts-ignore */
11746
- ...(ngDevMode ? [{ debugName: "shellClass" }] : /* istanbul ignore next */ []));
11747
11541
  describedBy = computed(() => {
11748
11542
  if (this.error()) {
11749
11543
  return this.errorId;
@@ -11754,58 +11548,357 @@ class SearchComboboxComponent {
11754
11548
  return null;
11755
11549
  }, /* @ts-ignore */
11756
11550
  ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
11757
- activeOptionId = computed(() => {
11758
- const index = this.activeIndex();
11759
- if (index < 0 || !this.open()) {
11760
- return null;
11761
- }
11762
- return this.optionId(index);
11763
- }, /* @ts-ignore */
11764
- ...(ngDevMode ? [{ debugName: "activeOptionId" }] : /* istanbul ignore next */ []));
11765
- selectedMark = computed(() => {
11766
- const sel = this.selected();
11767
- const fn = this.optionMark();
11768
- return !!sel && !!fn && fn(sel);
11769
- }, /* @ts-ignore */
11770
- ...(ngDevMode ? [{ debugName: "selectedMark" }] : /* istanbul ignore next */ []));
11771
- constructor() {
11772
- effect(() => {
11773
- const sel = this.selected();
11774
- const labelFn = this.optionLabel();
11775
- if (sel) {
11776
- this.query.set(labelFn(sel));
11777
- }
11778
- });
11779
- this.querySubject
11780
- .pipe(debounce(() => timer(Math.max(0, Number(this.debounceMs()) || 0))), distinctUntilChanged(), switchMap((text) => {
11781
- const trimmed = text.trim();
11782
- const min = Math.max(0, Number(this.minQueryLength()) || 0);
11783
- if (trimmed.length < min) {
11784
- this.options.set([]);
11785
- this.searchError.set(null);
11786
- this.loading.set(false);
11787
- this.open.set(false);
11788
- return of([]);
11789
- }
11790
- this.loading.set(true);
11791
- this.open.set(true);
11792
- return this.search()(trimmed).pipe(tap(() => this.searchError.set(null)), catchError((err) => {
11793
- this.searchError.set(err instanceof Error
11794
- ? err.message
11795
- : 'Could not load results.');
11796
- return of([]);
11797
- }));
11798
- }), takeUntilDestroyed(this.destroyRef))
11799
- .subscribe((results) => {
11800
- this.loading.set(false);
11801
- this.options.set(results);
11802
- this.activeIndex.set(results.length ? 0 : -1);
11803
- if (this.query().trim().length > 0) {
11804
- this.open.set(true);
11805
- }
11806
- });
11551
+ onChange = () => undefined;
11552
+ onTouched = () => undefined;
11553
+ trackOption(opt) {
11554
+ return String(opt.value);
11807
11555
  }
11808
- writeValue(value) {
11556
+ isSelected(opt) {
11557
+ return Object.is(this.value(), opt.value);
11558
+ }
11559
+ markClass(opt) {
11560
+ const base = 'pointer-events-none flex size-4 items-center justify-center rounded-full border-2 bg-transparent transition-colors ' +
11561
+ FORM_FOCUS_VISIBLE_PEER_CLASS;
11562
+ const err = this.error() ? ' !border-danger dark:!border-danger' : '';
11563
+ if (this.isSelected(opt)) {
11564
+ // Ring uses mode accent; fill stays transparent so the center dot reads clearly.
11565
+ return `${base} ${this.modeColor.classes().border}${err}`;
11566
+ }
11567
+ return `${base} border-neutral-400 dark:border-white${err}`;
11568
+ }
11569
+ /** Selected inner disc — SFN green / STN orange. */
11570
+ dotClass = computed(() => `block size-1.5 rounded-full ${this.modeColor.classes().bg}`, /* @ts-ignore */
11571
+ ...(ngDevMode ? [{ debugName: "dotClass" }] : /* istanbul ignore next */ []));
11572
+ /** @param value - Selected option value or `null`. */
11573
+ writeValue(value) {
11574
+ this.value.set(value);
11575
+ }
11576
+ /** @param fn - Selection change callback. */
11577
+ registerOnChange(fn) {
11578
+ this.onChange = fn;
11579
+ }
11580
+ /** @param fn - Blur callback. */
11581
+ registerOnTouched(fn) {
11582
+ this.onTouched = fn;
11583
+ }
11584
+ /** @param isDisabled - Blocks interaction when true. */
11585
+ setDisabledState(isDisabled) {
11586
+ this.cvaDisabled.set(isDisabled);
11587
+ }
11588
+ select(opt) {
11589
+ if (this.disabled() || opt.disabled) {
11590
+ return;
11591
+ }
11592
+ this.value.set(opt.value);
11593
+ this.onChange(opt.value);
11594
+ }
11595
+ onBlur() {
11596
+ this.onTouched();
11597
+ }
11598
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RadioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11599
+ 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: [
11600
+ {
11601
+ provide: NG_VALUE_ACCESSOR,
11602
+ useExisting: forwardRef(() => RadioComponent),
11603
+ multi: true,
11604
+ },
11605
+ ], ngImport: i0, template: `
11606
+ <fieldset
11607
+ class="m-0 p-0 border-0 min-w-0"
11608
+ [attr.aria-invalid]="error() ? true : null"
11609
+ [attr.aria-describedby]="describedBy()"
11610
+ >
11611
+ <legend [class]="labelClass">{{ label() }}</legend>
11612
+ <div class="flex flex-row flex-wrap gap-x-4 gap-y-2" role="radiogroup">
11613
+ @for (opt of options(); track trackOption(opt)) {
11614
+ <label
11615
+ class="inline-flex items-center gap-2.5 text-body text-ink dark:text-white"
11616
+ [class.opacity-50]="disabled() || opt.disabled"
11617
+ [class.cursor-pointer]="!(disabled() || opt.disabled)"
11618
+ [class.cursor-not-allowed]="disabled() || opt.disabled"
11619
+ >
11620
+ <span class="relative inline-flex size-4 shrink-0">
11621
+ <input
11622
+ type="radio"
11623
+ class="peer absolute inset-0 z-10 m-0 size-full opacity-0"
11624
+ [class.cursor-pointer]="!(disabled() || opt.disabled)"
11625
+ [class.cursor-not-allowed]="disabled() || opt.disabled"
11626
+ [name]="groupName"
11627
+ [checked]="isSelected(opt)"
11628
+ [disabled]="disabled() || !!opt.disabled"
11629
+ (change)="select(opt)"
11630
+ (blur)="onBlur()"
11631
+ />
11632
+ <span [class]="markClass(opt)" aria-hidden="true">
11633
+ @if (isSelected(opt)) {
11634
+ <span [class]="dotClass()"></span>
11635
+ }
11636
+ </span>
11637
+ </span>
11638
+ <span>{{ opt.label }}</span>
11639
+ </label>
11640
+ }
11641
+ </div>
11642
+ @if (error(); as err) {
11643
+ <p [id]="errorId" [class]="errorClass" role="alert">{{ err }}</p>
11644
+ } @else if (hint(); as h) {
11645
+ <p [id]="hintId" [class]="hintClass">{{ h }}</p>
11646
+ }
11647
+ </fieldset>
11648
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
11649
+ }
11650
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RadioComponent, decorators: [{
11651
+ type: Component,
11652
+ args: [{
11653
+ selector: 'africanies-radio',
11654
+ standalone: true,
11655
+ changeDetection: ChangeDetectionStrategy.OnPush,
11656
+ host: {
11657
+ class: 'inline-flex max-w-full',
11658
+ },
11659
+ providers: [
11660
+ {
11661
+ provide: NG_VALUE_ACCESSOR,
11662
+ useExisting: forwardRef(() => RadioComponent),
11663
+ multi: true,
11664
+ },
11665
+ ],
11666
+ template: `
11667
+ <fieldset
11668
+ class="m-0 p-0 border-0 min-w-0"
11669
+ [attr.aria-invalid]="error() ? true : null"
11670
+ [attr.aria-describedby]="describedBy()"
11671
+ >
11672
+ <legend [class]="labelClass">{{ label() }}</legend>
11673
+ <div class="flex flex-row flex-wrap gap-x-4 gap-y-2" role="radiogroup">
11674
+ @for (opt of options(); track trackOption(opt)) {
11675
+ <label
11676
+ class="inline-flex items-center gap-2.5 text-body text-ink dark:text-white"
11677
+ [class.opacity-50]="disabled() || opt.disabled"
11678
+ [class.cursor-pointer]="!(disabled() || opt.disabled)"
11679
+ [class.cursor-not-allowed]="disabled() || opt.disabled"
11680
+ >
11681
+ <span class="relative inline-flex size-4 shrink-0">
11682
+ <input
11683
+ type="radio"
11684
+ class="peer absolute inset-0 z-10 m-0 size-full opacity-0"
11685
+ [class.cursor-pointer]="!(disabled() || opt.disabled)"
11686
+ [class.cursor-not-allowed]="disabled() || opt.disabled"
11687
+ [name]="groupName"
11688
+ [checked]="isSelected(opt)"
11689
+ [disabled]="disabled() || !!opt.disabled"
11690
+ (change)="select(opt)"
11691
+ (blur)="onBlur()"
11692
+ />
11693
+ <span [class]="markClass(opt)" aria-hidden="true">
11694
+ @if (isSelected(opt)) {
11695
+ <span [class]="dotClass()"></span>
11696
+ }
11697
+ </span>
11698
+ </span>
11699
+ <span>{{ opt.label }}</span>
11700
+ </label>
11701
+ }
11702
+ </div>
11703
+ @if (error(); as err) {
11704
+ <p [id]="errorId" [class]="errorClass" role="alert">{{ err }}</p>
11705
+ } @else if (hint(); as h) {
11706
+ <p [id]="hintId" [class]="hintClass">{{ h }}</p>
11707
+ }
11708
+ </fieldset>
11709
+ `,
11710
+ }]
11711
+ }], 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 }] }] } });
11712
+
11713
+ /**
11714
+ * Radio form control exports.
11715
+ */
11716
+
11717
+ let nextSearchComboboxId = 0;
11718
+ const PANEL_POSITIONS = [
11719
+ {
11720
+ originX: 'start',
11721
+ originY: 'bottom',
11722
+ overlayX: 'start',
11723
+ overlayY: 'top',
11724
+ offsetY: 4,
11725
+ },
11726
+ {
11727
+ originX: 'start',
11728
+ originY: 'top',
11729
+ overlayX: 'start',
11730
+ overlayY: 'bottom',
11731
+ offsetY: -4,
11732
+ },
11733
+ ];
11734
+ /**
11735
+ * Generic type-as-you-search combobox with a debounced async lookup and
11736
+ * overlay result list.
11737
+ *
11738
+ * Pass {@link search} plus {@link optionLabel} (and optional
11739
+ * {@link optionSubtitle}) so the same control works for customers, staff,
11740
+ * partners, etc. Emits the selected entity via `[(selected)]` / CVA.
11741
+ *
11742
+ * @typeParam T - Entity type returned by {@link search} and held in
11743
+ * {@link selected}.
11744
+ *
11745
+ * @example
11746
+ * ```html
11747
+ * <africanies-search-combobox
11748
+ * label="Customer"
11749
+ * [search]="searchCustomers"
11750
+ * [optionLabel]="customerLabel"
11751
+ * [optionSubtitle]="customerSubtitle"
11752
+ * [(selected)]="customer"
11753
+ * />
11754
+ * ```
11755
+ */
11756
+ class SearchComboboxComponent {
11757
+ destroyRef = inject(DestroyRef);
11758
+ controlId = `africanies-search-combobox-${++nextSearchComboboxId}`;
11759
+ hintId = `${this.controlId}-hint`;
11760
+ errorId = `${this.controlId}-error`;
11761
+ listboxId = `${this.controlId}-listbox`;
11762
+ labelClass = FORM_LABEL_CLASS;
11763
+ affixClass = FORM_AFFIX_CLASS;
11764
+ innerClass = FORM_CONTROL_INNER_CLASS;
11765
+ hintClass = FORM_HINT_CLASS;
11766
+ errorClass = FORM_ERROR_CLASS;
11767
+ panelPositions = PANEL_POSITIONS;
11768
+ minPanelWidth = 224;
11769
+ triggerOrigin = viewChild(CdkOverlayOrigin, /* @ts-ignore */
11770
+ ...(ngDevMode ? [{ debugName: "triggerOrigin" }] : /* istanbul ignore next */ []));
11771
+ querySubject = new Subject();
11772
+ onChange = () => undefined;
11773
+ onTouched = () => undefined;
11774
+ /** Visible field label. */
11775
+ label = input('', /* @ts-ignore */
11776
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
11777
+ /** Helper copy under the field. Hidden while {@link error} is set. */
11778
+ hint = input(undefined, /* @ts-ignore */
11779
+ ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
11780
+ /** Field-level validation message. */
11781
+ error = input(null, /* @ts-ignore */
11782
+ ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
11783
+ placeholder = input('', /* @ts-ignore */
11784
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
11785
+ /** Debounce before calling {@link search} (ms). Default 500. */
11786
+ debounceMs = input(500, { ...(ngDevMode ? { debugName: "debounceMs" } : /* istanbul ignore next */ {}), transform: numberAttribute });
11787
+ /** Minimum trimmed query length before search runs. Default 2. */
11788
+ minQueryLength = input(2, { ...(ngDevMode ? { debugName: "minQueryLength" } : /* istanbul ignore next */ {}), transform: numberAttribute });
11789
+ emptyMessage = input('No matches', /* @ts-ignore */
11790
+ ...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
11791
+ /** Async lookup — parent owns HTTP / filtering. */
11792
+ search = input.required(/* @ts-ignore */
11793
+ ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
11794
+ optionLabel = input.required(/* @ts-ignore */
11795
+ ...(ngDevMode ? [{ debugName: "optionLabel" }] : /* istanbul ignore next */ []));
11796
+ optionSubtitle = input(undefined, /* @ts-ignore */
11797
+ ...(ngDevMode ? [{ debugName: "optionSubtitle" }] : /* istanbul ignore next */ []));
11798
+ optionBadge = input(undefined, /* @ts-ignore */
11799
+ ...(ngDevMode ? [{ debugName: "optionBadge" }] : /* istanbul ignore next */ []));
11800
+ optionMark = input(undefined, /* @ts-ignore */
11801
+ ...(ngDevMode ? [{ debugName: "optionMark" }] : /* istanbul ignore next */ []));
11802
+ markTooltip = input('Professional Business Plan', /* @ts-ignore */
11803
+ ...(ngDevMode ? [{ debugName: "markTooltip" }] : /* istanbul ignore next */ []));
11804
+ trackBy = input((item) => this.optionLabel()(item), /* @ts-ignore */
11805
+ ...(ngDevMode ? [{ debugName: "trackBy" }] : /* istanbul ignore next */ []));
11806
+ selected = model(null, /* @ts-ignore */
11807
+ ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
11808
+ queryChange = output();
11809
+ disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled',
11810
+ transform: booleanAttribute });
11811
+ cvaDisabled = signal(false, /* @ts-ignore */
11812
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
11813
+ disabled = computed(() => this.disabledInput() || this.cvaDisabled(), /* @ts-ignore */
11814
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
11815
+ query = signal('', /* @ts-ignore */
11816
+ ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
11817
+ open = signal(false, /* @ts-ignore */
11818
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
11819
+ loading = signal(false, /* @ts-ignore */
11820
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
11821
+ options = signal([], /* @ts-ignore */
11822
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
11823
+ searchError = signal(null, /* @ts-ignore */
11824
+ ...(ngDevMode ? [{ debugName: "searchError" }] : /* istanbul ignore next */ []));
11825
+ activeIndex = signal(-1, /* @ts-ignore */
11826
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
11827
+ panelWidth = signal(this.minPanelWidth, /* @ts-ignore */
11828
+ ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
11829
+ shellClass = computed(() => {
11830
+ let classes = FORM_FIELD_CLASS;
11831
+ if (this.error()) {
11832
+ classes += ` ${FORM_FIELD_ERROR_CLASS}`;
11833
+ }
11834
+ if (this.disabled()) {
11835
+ classes += ` ${FORM_DISABLED_CLASS}`;
11836
+ }
11837
+ return classes;
11838
+ }, /* @ts-ignore */
11839
+ ...(ngDevMode ? [{ debugName: "shellClass" }] : /* istanbul ignore next */ []));
11840
+ describedBy = computed(() => {
11841
+ if (this.error()) {
11842
+ return this.errorId;
11843
+ }
11844
+ if (this.hint()) {
11845
+ return this.hintId;
11846
+ }
11847
+ return null;
11848
+ }, /* @ts-ignore */
11849
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
11850
+ activeOptionId = computed(() => {
11851
+ const index = this.activeIndex();
11852
+ if (index < 0 || !this.open()) {
11853
+ return null;
11854
+ }
11855
+ return this.optionId(index);
11856
+ }, /* @ts-ignore */
11857
+ ...(ngDevMode ? [{ debugName: "activeOptionId" }] : /* istanbul ignore next */ []));
11858
+ selectedMark = computed(() => {
11859
+ const sel = this.selected();
11860
+ const fn = this.optionMark();
11861
+ return !!sel && !!fn && fn(sel);
11862
+ }, /* @ts-ignore */
11863
+ ...(ngDevMode ? [{ debugName: "selectedMark" }] : /* istanbul ignore next */ []));
11864
+ constructor() {
11865
+ effect(() => {
11866
+ const sel = this.selected();
11867
+ const labelFn = this.optionLabel();
11868
+ if (sel) {
11869
+ this.query.set(labelFn(sel));
11870
+ }
11871
+ });
11872
+ this.querySubject
11873
+ .pipe(debounce(() => timer(Math.max(0, Number(this.debounceMs()) || 0))), distinctUntilChanged(), switchMap((text) => {
11874
+ const trimmed = text.trim();
11875
+ const min = Math.max(0, Number(this.minQueryLength()) || 0);
11876
+ if (trimmed.length < min) {
11877
+ this.options.set([]);
11878
+ this.searchError.set(null);
11879
+ this.loading.set(false);
11880
+ this.open.set(false);
11881
+ return of([]);
11882
+ }
11883
+ this.loading.set(true);
11884
+ this.open.set(true);
11885
+ return this.search()(trimmed).pipe(tap(() => this.searchError.set(null)), catchError((err) => {
11886
+ this.searchError.set(err instanceof Error
11887
+ ? err.message
11888
+ : 'Could not load results.');
11889
+ return of([]);
11890
+ }));
11891
+ }), takeUntilDestroyed(this.destroyRef))
11892
+ .subscribe((results) => {
11893
+ this.loading.set(false);
11894
+ this.options.set(results);
11895
+ this.activeIndex.set(results.length ? 0 : -1);
11896
+ if (this.query().trim().length > 0) {
11897
+ this.open.set(true);
11898
+ }
11899
+ });
11900
+ }
11901
+ writeValue(value) {
11809
11902
  this.selected.set(value);
11810
11903
  this.query.set(value ? this.optionLabel()(value) : '');
11811
11904
  }
@@ -12091,7 +12184,7 @@ class SearchComboboxComponent {
12091
12184
  } @else if (hint(); as h) {
12092
12185
  <p [id]="hintId" [class]="hintClass">{{ h }}</p>
12093
12186
  }
12094
- `, isInline: true, dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: ChipComponent, selector: "africanies-chip", inputs: ["variant", "size", "icon", "removable", "removeLabel"], outputs: ["removed"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "component", type: TooltipComponent, selector: "africanies-tooltip", inputs: ["text", "placement", "icon", "ariaLabel", "disabled"] }, { kind: "directive", type: TooltipTriggerDirective, selector: "[africaniesTooltipTrigger]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12187
+ `, isInline: true, dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: ChipComponent, selector: "africanies-chip", inputs: ["variant", "color", "size", "icon", "removable", "removeLabel"], outputs: ["removed"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "component", type: TooltipComponent, selector: "africanies-tooltip", inputs: ["text", "placement", "icon", "ariaLabel", "disabled"] }, { kind: "directive", type: TooltipTriggerDirective, selector: "[africaniesTooltipTrigger]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12095
12188
  }
12096
12189
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SearchComboboxComponent, decorators: [{
12097
12190
  type: Component,
@@ -12603,7 +12696,7 @@ class ToggleComponent {
12603
12696
  [id]="controlId"
12604
12697
  type="button"
12605
12698
  role="switch"
12606
- 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"
12699
+ 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"
12607
12700
  [ngClass]="trackClass()"
12608
12701
  [attr.aria-checked]="on()"
12609
12702
  [attr.aria-busy]="loading() ? true : null"
@@ -12665,7 +12758,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
12665
12758
  [id]="controlId"
12666
12759
  type="button"
12667
12760
  role="switch"
12668
- 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"
12761
+ 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"
12669
12762
  [ngClass]="trackClass()"
12670
12763
  [attr.aria-checked]="on()"
12671
12764
  [attr.aria-busy]="loading() ? true : null"
@@ -13215,6 +13308,10 @@ const DEFAULT_LINK_ACTIVE$2 = {
13215
13308
  /**
13216
13309
  * Compact segmented control for view modes and filters.
13217
13310
  *
13311
+ * Selected option uses a **solid brand pill** (white label) on a neutral track —
13312
+ * same pattern as the portal shipping-mode toggle. With exactly two options the
13313
+ * pill slides; with more options each selected chip fills in place.
13314
+ *
13218
13315
  * ## Router mode (URL is source of truth)
13219
13316
  *
13220
13317
  * Set `routerLink` (and optional `queryParams`) on items. The selected pill
@@ -13264,15 +13361,16 @@ class SegmentComponent {
13264
13361
  */
13265
13362
  keepScroll = input(true, { ...(ngDevMode ? { debugName: "keepScroll" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
13266
13363
  url = toSignal(this.router.events.pipe(filter((e) => e instanceof NavigationEnd), map(() => this.router.url), startWith(this.router.url)), { initialValue: this.router.url });
13267
- segmentChrome = computed(() => 'inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-body-sm transition-colors duration-150 no-underline', /* @ts-ignore */
13268
- ...(ngDevMode ? [{ debugName: "segmentChrome" }] : /* istanbul ignore next */ []));
13269
- selectedClass = computed(() => {
13270
- const colors = this.modeColor.classes();
13271
- return `${colors.soft} ${colors.text} shadow-sm font-semibold`;
13364
+ /** Two equal options portal-style sliding brand pill. */
13365
+ useSlidingThumb = computed(() => this.items().length === 2, /* @ts-ignore */
13366
+ ...(ngDevMode ? [{ debugName: "useSlidingThumb" }] : /* istanbul ignore next */ []));
13367
+ /** Index of the active option (0 / 1 for the sliding thumb). */
13368
+ activeIndex = computed(() => {
13369
+ const list = this.items();
13370
+ const idx = list.findIndex((item) => this.isItemActive(item));
13371
+ return idx < 0 ? 0 : idx;
13272
13372
  }, /* @ts-ignore */
13273
- ...(ngDevMode ? [{ debugName: "selectedClass" }] : /* istanbul ignore next */ []));
13274
- idleClass = computed(() => 'font-medium text-neutral-600 hover:text-ink dark:text-neutral-400 dark:hover:text-white', /* @ts-ignore */
13275
- ...(ngDevMode ? [{ debugName: "idleClass" }] : /* istanbul ignore next */ []));
13373
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
13276
13374
  constructor() {
13277
13375
  effect(() => {
13278
13376
  this.url();
@@ -13297,6 +13395,56 @@ class SegmentComponent {
13297
13395
  }
13298
13396
  return this.activeId() === item.id;
13299
13397
  }
13398
+ /**
13399
+ * Sliding thumb fill. STN / SFN options keep their own brand color so the
13400
+ * pill matches mode identity; other pairs follow the active theme primary.
13401
+ */
13402
+ thumbFillClass() {
13403
+ const active = this.items()[this.activeIndex()];
13404
+ return this.brandFillFor(active?.id ?? null);
13405
+ }
13406
+ /**
13407
+ * Option chrome: sliding mode keeps labels transparent over the thumb;
13408
+ * multi-option mode paints a solid selected chip in place.
13409
+ *
13410
+ * @param item - Option being rendered.
13411
+ * @returns Class string for the control.
13412
+ */
13413
+ optionChrome(item) {
13414
+ const base = 'relative z-10 inline-flex min-w-0 items-center justify-center gap-1.5 rounded-md px-3 py-1.5 text-center text-body-sm no-underline transition-colors duration-150';
13415
+ const active = this.isItemActive(item);
13416
+ if (this.useSlidingThumb()) {
13417
+ return (base +
13418
+ ' w-full ' +
13419
+ (active
13420
+ ? 'font-semibold text-white'
13421
+ : 'font-medium text-ink hover:text-ink dark:text-neutral-300 dark:hover:text-white'));
13422
+ }
13423
+ if (active) {
13424
+ return `${base} font-semibold text-white shadow-sm ${this.brandFillFor(item.id)}`;
13425
+ }
13426
+ return (base +
13427
+ ' font-medium text-ink hover:bg-white/70 hover:text-ink dark:text-neutral-300 dark:hover:bg-white/10 dark:hover:text-white');
13428
+ }
13429
+ /**
13430
+ * Solid brand fill for a selected option / sliding thumb.
13431
+ *
13432
+ * Light + dark use the same SFN green / STN orange primaries so the control
13433
+ * stays mode-identifiable on both surfaces (portal primary, not soft tint).
13434
+ * Class strings are full literals so Tailwind keeps them.
13435
+ *
13436
+ * @param id - Option id (`stn` / `sfn` get fixed brands; else theme primary).
13437
+ * @returns Background utility classes.
13438
+ */
13439
+ brandFillFor(id) {
13440
+ if (id === 'stn') {
13441
+ return 'bg-import dark:bg-import';
13442
+ }
13443
+ if (id === 'sfn') {
13444
+ return 'bg-export dark:bg-export';
13445
+ }
13446
+ return this.modeColor.classes().activeFill;
13447
+ }
13300
13448
  selectLocal(item) {
13301
13449
  if (item.disabled || item.routerLink != null) {
13302
13450
  return;
@@ -13323,18 +13471,31 @@ class SegmentComponent {
13323
13471
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SegmentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13324
13472
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SegmentComponent, isStandalone: true, selector: "africanies-segment", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, activeId: { classPropertyName: "activeId", publicName: "activeId", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, linkActiveOptions: { classPropertyName: "linkActiveOptions", publicName: "linkActiveOptions", isSignal: true, isRequired: false, transformFunction: null }, keepScroll: { classPropertyName: "keepScroll", publicName: "keepScroll", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeId: "activeIdChange" }, ngImport: i0, template: `
13325
13473
  <div
13326
- class="inline-flex max-w-full flex-wrap gap-1 rounded-lg bg-background-welcome p-1 dark:bg-ink-950"
13474
+ class="relative inline-grid max-w-full rounded-lg bg-[#f0f2f5] p-1 dark:bg-[#2a2c31]"
13327
13475
  role="group"
13328
13476
  [attr.aria-label]="ariaLabel()"
13477
+ [class.grid-cols-2]="useSlidingThumb()"
13478
+ [class.inline-flex]="!useSlidingThumb()"
13479
+ [class.flex-wrap]="!useSlidingThumb()"
13480
+ [class.gap-1]="!useSlidingThumb()"
13329
13481
  >
13482
+ @if (useSlidingThumb()) {
13483
+ <div
13484
+ class="africanies-segment-thumb pointer-events-none absolute top-1 bottom-1 left-1 rounded-md shadow-sm transition-transform duration-200 ease-out motion-reduce:transition-none"
13485
+ [class]="thumbFillClass()"
13486
+ [style.transform]="
13487
+ activeIndex() === 1
13488
+ ? 'translateX(calc(100% + 0.25rem))'
13489
+ : 'translateX(0)'
13490
+ "
13491
+ aria-hidden="true"
13492
+ ></div>
13493
+ }
13494
+
13330
13495
  @for (item of items(); track item.id) {
13331
13496
  @if (item.routerLink !== null && item.routerLink !== undefined) {
13332
13497
  <a
13333
- [class]="
13334
- segmentChrome() +
13335
- ' ' +
13336
- (isItemActive(item) ? selectedClass() : idleClass())
13337
- "
13498
+ [class]="optionChrome(item)"
13338
13499
  [class.cursor-pointer]="!item.disabled"
13339
13500
  [class.cursor-not-allowed]="item.disabled"
13340
13501
  [class.opacity-50]="item.disabled"
@@ -13351,11 +13512,7 @@ class SegmentComponent {
13351
13512
  } @else {
13352
13513
  <button
13353
13514
  type="button"
13354
- [class]="
13355
- segmentChrome() +
13356
- ' ' +
13357
- (isItemActive(item) ? selectedClass() : idleClass())
13358
- "
13515
+ [class]="optionChrome(item)"
13359
13516
  [disabled]="!!item.disabled"
13360
13517
  [class.cursor-pointer]="!item.disabled"
13361
13518
  [class.cursor-not-allowed]="item.disabled"
@@ -13370,29 +13527,37 @@ class SegmentComponent {
13370
13527
  }
13371
13528
  }
13372
13529
  </div>
13373
- `, isInline: true, dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13530
+ `, isInline: true, styles: [":host .africanies-segment-thumb{width:calc((100% - .5rem)/2)}\n"], dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13374
13531
  }
13375
13532
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SegmentComponent, decorators: [{
13376
13533
  type: Component,
13377
- args: [{
13378
- selector: 'africanies-segment',
13379
- standalone: true,
13380
- changeDetection: ChangeDetectionStrategy.OnPush,
13381
- imports: [AfricaniesIconComponent],
13382
- template: `
13534
+ args: [{ selector: 'africanies-segment', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AfricaniesIconComponent], template: `
13383
13535
  <div
13384
- class="inline-flex max-w-full flex-wrap gap-1 rounded-lg bg-background-welcome p-1 dark:bg-ink-950"
13536
+ class="relative inline-grid max-w-full rounded-lg bg-[#f0f2f5] p-1 dark:bg-[#2a2c31]"
13385
13537
  role="group"
13386
13538
  [attr.aria-label]="ariaLabel()"
13539
+ [class.grid-cols-2]="useSlidingThumb()"
13540
+ [class.inline-flex]="!useSlidingThumb()"
13541
+ [class.flex-wrap]="!useSlidingThumb()"
13542
+ [class.gap-1]="!useSlidingThumb()"
13387
13543
  >
13544
+ @if (useSlidingThumb()) {
13545
+ <div
13546
+ class="africanies-segment-thumb pointer-events-none absolute top-1 bottom-1 left-1 rounded-md shadow-sm transition-transform duration-200 ease-out motion-reduce:transition-none"
13547
+ [class]="thumbFillClass()"
13548
+ [style.transform]="
13549
+ activeIndex() === 1
13550
+ ? 'translateX(calc(100% + 0.25rem))'
13551
+ : 'translateX(0)'
13552
+ "
13553
+ aria-hidden="true"
13554
+ ></div>
13555
+ }
13556
+
13388
13557
  @for (item of items(); track item.id) {
13389
13558
  @if (item.routerLink !== null && item.routerLink !== undefined) {
13390
13559
  <a
13391
- [class]="
13392
- segmentChrome() +
13393
- ' ' +
13394
- (isItemActive(item) ? selectedClass() : idleClass())
13395
- "
13560
+ [class]="optionChrome(item)"
13396
13561
  [class.cursor-pointer]="!item.disabled"
13397
13562
  [class.cursor-not-allowed]="item.disabled"
13398
13563
  [class.opacity-50]="item.disabled"
@@ -13409,11 +13574,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
13409
13574
  } @else {
13410
13575
  <button
13411
13576
  type="button"
13412
- [class]="
13413
- segmentChrome() +
13414
- ' ' +
13415
- (isItemActive(item) ? selectedClass() : idleClass())
13416
- "
13577
+ [class]="optionChrome(item)"
13417
13578
  [disabled]="!!item.disabled"
13418
13579
  [class.cursor-pointer]="!item.disabled"
13419
13580
  [class.cursor-not-allowed]="item.disabled"
@@ -13428,10 +13589,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
13428
13589
  }
13429
13590
  }
13430
13591
  </div>
13431
- `,
13432
- }]
13592
+ `, styles: [":host .africanies-segment-thumb{width:calc((100% - .5rem)/2)}\n"] }]
13433
13593
  }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], 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 }] }], linkActiveOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "linkActiveOptions", required: false }] }], keepScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepScroll", required: false }] }] } });
13434
13594
 
13595
+ const PANEL_OPEN_STORAGE_KEY = 'africanies-shipping-mode-panel-open';
13596
+ function readStoredPanelOpen() {
13597
+ if (typeof localStorage === 'undefined') {
13598
+ return true;
13599
+ }
13600
+ try {
13601
+ const raw = localStorage.getItem(PANEL_OPEN_STORAGE_KEY);
13602
+ if (raw === null) {
13603
+ return true;
13604
+ }
13605
+ return raw !== '0' && raw !== 'false';
13606
+ }
13607
+ catch {
13608
+ return true;
13609
+ }
13610
+ }
13611
+ function writeStoredPanelOpen(open) {
13612
+ if (typeof localStorage === 'undefined') {
13613
+ return;
13614
+ }
13615
+ try {
13616
+ localStorage.setItem(PANEL_OPEN_STORAGE_KEY, open ? '1' : '0');
13617
+ }
13618
+ catch {
13619
+ // Private mode / quota — panel still toggles in-session.
13620
+ }
13621
+ }
13435
13622
  /**
13436
13623
  * Shared globe + plane glyph for STN / SFN. STN paints it rotated 180°.
13437
13624
  *
@@ -13446,12 +13633,13 @@ const SHIPPING_MODE_GLYPH = `M88.42,68.75a48.8,48.8,0,0,1-4.74,11.1h9.89a7.67,7.
13446
13633
  * import orange; **from Nigeria** uses export green. Both tiles keep equal
13447
13634
  * chrome so unselected does not look like floating text.
13448
13635
  *
13449
- * Project into `<africanies-side-nav>` with the `footer` attribute.
13636
+ * Project into `<africanies-side-nav>` with the `pre-nav` attribute (above the
13637
+ * nav list). `footer` still works for legacy placement under the list.
13450
13638
  *
13451
13639
  * @example
13452
13640
  * ```html
13453
13641
  * <africanies-side-nav [items]="nav" [(collapsed)]="collapsed">
13454
- * <africanies-shipping-mode-switch footer [collapsed]="collapsed()" />
13642
+ * <africanies-shipping-mode-switch pre-nav [collapsed]="collapsed()" />
13455
13643
  * </africanies-side-nav>
13456
13644
  * ```
13457
13645
  */
@@ -13472,13 +13660,32 @@ class ShippingModeSwitchComponent {
13472
13660
  * Icon-only stacked tiles when the side nav rail is collapsed.
13473
13661
  */
13474
13662
  collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
13663
+ /**
13664
+ * When true, the mode tiles are visible. Persisted in `localStorage`.
13665
+ * Two-way bindable for hosts that need to force-open (e.g. onboarding).
13666
+ */
13667
+ panelOpen = model(readStoredPanelOpen(), /* @ts-ignore */
13668
+ ...(ngDevMode ? [{ debugName: "panelOpen" }] : /* istanbul ignore next */ []));
13475
13669
  /** Active mode from {@link ShippingModeService}. */
13476
13670
  mode = this.shipping.mode;
13477
- /** Host padding / divider tighter when the rail is collapsed. */
13671
+ modeChipLabel = computed(() => this.mode() === 'stn' ? 'STN' : 'SFN', /* @ts-ignore */
13672
+ ...(ngDevMode ? [{ debugName: "modeChipLabel" }] : /* istanbul ignore next */ []));
13673
+ modeChipVariant = computed(() => (this.mode() === 'stn' ? 'import' : 'export'), /* @ts-ignore */
13674
+ ...(ngDevMode ? [{ debugName: "modeChipVariant" }] : /* istanbul ignore next */ []));
13675
+ /** Host padding / divider — sits above the nav list (`border-b`). */
13478
13676
  hostClass = computed(() => this.collapsed()
13479
- ? 'block shrink-0 border-t border-strong px-1.5 py-3 dark:border-white/10'
13480
- : 'block shrink-0 border-t border-strong px-2 py-3 dark:border-white/10', /* @ts-ignore */
13677
+ ? 'block shrink-0 border-b border-border px-1.5 py-2 dark:border-white/10'
13678
+ : 'block shrink-0 border-b border-border px-2 py-2.5 dark:border-white/10', /* @ts-ignore */
13481
13679
  ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
13680
+ constructor() {
13681
+ effect(() => {
13682
+ writeStoredPanelOpen(this.panelOpen());
13683
+ });
13684
+ }
13685
+ /** Show or hide the mode tiles. */
13686
+ togglePanel() {
13687
+ this.panelOpen.update((open) => !open);
13688
+ }
13482
13689
  /**
13483
13690
  * Requests a mode change through {@link ShippingModeService.requestModeChange}
13484
13691
  * so feature guards (e.g. Create Shipment confirm) can run first.
@@ -13501,82 +13708,153 @@ class ShippingModeSwitchComponent {
13501
13708
  const selected = this.mode() === option;
13502
13709
  const compact = this.collapsed();
13503
13710
  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 ' +
13504
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink ' +
13505
- (compact ? 'min-h-9 px-1 py-1.5' : 'min-h-[4.5rem] px-1.5 py-2');
13506
- 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';
13711
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus ' +
13712
+ (compact ? 'size-9 min-h-0 px-0 py-0' : 'min-h-[4.5rem] px-1.5 py-2');
13713
+ 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';
13507
13714
  if (option === 'stn') {
13508
13715
  return selected
13509
- ? `${base} border-import bg-import text-white`
13716
+ ? `${base} border-import-strong bg-import-strong text-white dark:border-import dark:bg-import`
13510
13717
  : `${base} ${idle}`;
13511
13718
  }
13512
13719
  return selected
13513
- ? `${base} border-export bg-export text-white`
13720
+ ? `${base} border-export bg-export text-white dark:border-export dark:bg-export`
13514
13721
  : `${base} ${idle}`;
13515
13722
  }
13516
13723
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ShippingModeSwitchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13517
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ShippingModeSwitchComponent, isStandalone: true, selector: "africanies-shipping-mode-switch", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClass()" } }, ngImport: i0, template: `
13518
- @if (!collapsed()) {
13519
- <p
13520
- class="m-0 mb-2 px-1 text-caption font-semibold uppercase tracking-wide text-neutral-600 dark:text-neutral-400"
13521
- >
13522
- Shipping mode
13523
- </p>
13524
- }
13525
- <div
13526
- class="grid gap-1.5"
13527
- [class.grid-cols-1]="collapsed()"
13528
- [class.grid-cols-2]="!collapsed()"
13529
- role="radiogroup"
13530
- aria-label="Shipping mode"
13531
- >
13532
- <button
13533
- type="button"
13534
- role="radio"
13535
- [attr.aria-checked]="mode() === 'stn'"
13536
- aria-label="Shipping to Nigeria"
13537
- [class]="cardClass('stn')"
13538
- (click)="select('stn')"
13724
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ShippingModeSwitchComponent, isStandalone: true, selector: "africanies-shipping-mode-switch", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, panelOpen: { classPropertyName: "panelOpen", publicName: "panelOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { panelOpen: "panelOpenChange" }, host: { properties: { "class": "hostClass()" } }, ngImport: i0, template: `
13725
+ @if (collapsed()) {
13726
+ <!-- Narrow rail: icon tiles only — chip + chevron reads as cramped. -->
13727
+ <div
13728
+ class="grid grid-cols-1 gap-1.5"
13729
+ role="radiogroup"
13730
+ aria-label="Shipping mode"
13539
13731
  >
13540
- <svg
13541
- viewBox="0 0 122.88 107.54"
13542
- class="h-6 w-6 shrink-0 rotate-180"
13543
- aria-hidden="true"
13544
- focusable="false"
13732
+ <button
13733
+ type="button"
13734
+ role="radio"
13735
+ [attr.aria-checked]="mode() === 'stn'"
13736
+ aria-label="Shipping to Nigeria"
13737
+ title="Shipping to Nigeria"
13738
+ [class]="cardClass('stn')"
13739
+ (click)="select('stn')"
13545
13740
  >
13546
- <path [attr.d]="glyphPath()" fill="currentColor" />
13547
- </svg>
13548
- @if (!collapsed()) {
13549
- <span class="flex flex-col leading-tight">
13550
- <span>Shipping</span>
13551
- <span>to Nigeria</span>
13552
- </span>
13553
- }
13554
- </button>
13555
- <button
13556
- type="button"
13557
- role="radio"
13558
- [attr.aria-checked]="mode() === 'sfn'"
13559
- aria-label="Shipping from Nigeria"
13560
- [class]="cardClass('sfn')"
13561
- (click)="select('sfn')"
13562
- >
13563
- <svg
13564
- viewBox="0 0 122.88 107.54"
13565
- class="h-6 w-6 shrink-0"
13566
- aria-hidden="true"
13567
- focusable="false"
13741
+ <svg
13742
+ viewBox="0 0 122.88 107.54"
13743
+ class="h-5 w-5 shrink-0 rotate-180"
13744
+ aria-hidden="true"
13745
+ focusable="false"
13746
+ >
13747
+ <path [attr.d]="glyphPath()" fill="currentColor" />
13748
+ </svg>
13749
+ </button>
13750
+ <button
13751
+ type="button"
13752
+ role="radio"
13753
+ [attr.aria-checked]="mode() === 'sfn'"
13754
+ aria-label="Shipping from Nigeria"
13755
+ title="Shipping from Nigeria"
13756
+ [class]="cardClass('sfn')"
13757
+ (click)="select('sfn')"
13568
13758
  >
13569
- <path [attr.d]="glyphPath()" fill="currentColor" />
13570
- </svg>
13571
- @if (!collapsed()) {
13572
- <span class="flex flex-col leading-tight">
13573
- <span>Shipping</span>
13574
- <span>from Nigeria</span>
13575
- </span>
13759
+ <svg
13760
+ viewBox="0 0 122.88 107.54"
13761
+ class="h-5 w-5 shrink-0"
13762
+ aria-hidden="true"
13763
+ focusable="false"
13764
+ >
13765
+ <path [attr.d]="glyphPath()" fill="currentColor" />
13766
+ </svg>
13767
+ </button>
13768
+ </div>
13769
+ } @else {
13770
+ <div class="mb-2 flex items-center gap-1.5">
13771
+ <p
13772
+ class="m-0 shrink-0 px-1 text-caption font-semibold uppercase tracking-wide text-neutral-600 dark:text-neutral-400"
13773
+ >
13774
+ Shipping mode
13775
+ </p>
13776
+ <!-- STN / SFN chip only when the tiles are collapsed — open panel already shows both modes. -->
13777
+ @if (!panelOpen()) {
13778
+ <africanies-chip [variant]="modeChipVariant()" size="sm">
13779
+ {{ modeChipLabel() }}
13780
+ </africanies-chip>
13576
13781
  }
13577
- </button>
13578
- </div>
13579
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
13782
+ <span class="min-w-0 flex-1" aria-hidden="true"></span>
13783
+ <button
13784
+ type="button"
13785
+ class="inline-flex size-7 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"
13786
+ [attr.aria-label]="
13787
+ panelOpen() ? 'Hide shipping modes' : 'Show shipping modes'
13788
+ "
13789
+ [attr.aria-expanded]="panelOpen()"
13790
+ (click)="togglePanel()"
13791
+ >
13792
+ <africanies-icon
13793
+ [name]="panelOpen() ? 'chevron-up' : 'chevron-down'"
13794
+ [size]="14"
13795
+ />
13796
+ </button>
13797
+ </div>
13798
+
13799
+ <div
13800
+ class="grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none"
13801
+ [style.grid-template-rows]="panelOpen() ? '1fr' : '0fr'"
13802
+ [attr.aria-hidden]="!panelOpen()"
13803
+ [class.pointer-events-none]="!panelOpen()"
13804
+ >
13805
+ <div class="min-h-0 overflow-hidden">
13806
+ <div
13807
+ class="grid grid-cols-2 gap-1.5"
13808
+ role="radiogroup"
13809
+ aria-label="Shipping mode"
13810
+ >
13811
+ <button
13812
+ type="button"
13813
+ role="radio"
13814
+ [attr.aria-checked]="mode() === 'stn'"
13815
+ aria-label="Shipping to Nigeria"
13816
+ [class]="cardClass('stn')"
13817
+ (click)="select('stn')"
13818
+ >
13819
+ <svg
13820
+ viewBox="0 0 122.88 107.54"
13821
+ class="h-6 w-6 shrink-0 rotate-180"
13822
+ aria-hidden="true"
13823
+ focusable="false"
13824
+ >
13825
+ <path [attr.d]="glyphPath()" fill="currentColor" />
13826
+ </svg>
13827
+ <span class="flex flex-col leading-tight">
13828
+ <span>Shipping</span>
13829
+ <span>to Nigeria</span>
13830
+ </span>
13831
+ </button>
13832
+ <button
13833
+ type="button"
13834
+ role="radio"
13835
+ [attr.aria-checked]="mode() === 'sfn'"
13836
+ aria-label="Shipping from Nigeria"
13837
+ [class]="cardClass('sfn')"
13838
+ (click)="select('sfn')"
13839
+ >
13840
+ <svg
13841
+ viewBox="0 0 122.88 107.54"
13842
+ class="h-6 w-6 shrink-0"
13843
+ aria-hidden="true"
13844
+ focusable="false"
13845
+ >
13846
+ <path [attr.d]="glyphPath()" fill="currentColor" />
13847
+ </svg>
13848
+ <span class="flex flex-col leading-tight">
13849
+ <span>Shipping</span>
13850
+ <span>from Nigeria</span>
13851
+ </span>
13852
+ </button>
13853
+ </div>
13854
+ </div>
13855
+ </div>
13856
+ }
13857
+ `, isInline: true, dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: ChipComponent, selector: "africanies-chip", inputs: ["variant", "color", "size", "icon", "removable", "removeLabel"], outputs: ["removed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13580
13858
  }
13581
13859
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ShippingModeSwitchComponent, decorators: [{
13582
13860
  type: Component,
@@ -13584,74 +13862,146 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
13584
13862
  selector: 'africanies-shipping-mode-switch',
13585
13863
  standalone: true,
13586
13864
  changeDetection: ChangeDetectionStrategy.OnPush,
13865
+ imports: [AfricaniesIconComponent, ChipComponent],
13587
13866
  host: {
13588
13867
  '[class]': 'hostClass()',
13589
13868
  },
13590
13869
  template: `
13591
- @if (!collapsed()) {
13592
- <p
13593
- class="m-0 mb-2 px-1 text-caption font-semibold uppercase tracking-wide text-neutral-600 dark:text-neutral-400"
13594
- >
13595
- Shipping mode
13596
- </p>
13597
- }
13598
- <div
13599
- class="grid gap-1.5"
13600
- [class.grid-cols-1]="collapsed()"
13601
- [class.grid-cols-2]="!collapsed()"
13602
- role="radiogroup"
13603
- aria-label="Shipping mode"
13604
- >
13605
- <button
13606
- type="button"
13607
- role="radio"
13608
- [attr.aria-checked]="mode() === 'stn'"
13609
- aria-label="Shipping to Nigeria"
13610
- [class]="cardClass('stn')"
13611
- (click)="select('stn')"
13870
+ @if (collapsed()) {
13871
+ <!-- Narrow rail: icon tiles only — chip + chevron reads as cramped. -->
13872
+ <div
13873
+ class="grid grid-cols-1 gap-1.5"
13874
+ role="radiogroup"
13875
+ aria-label="Shipping mode"
13612
13876
  >
13613
- <svg
13614
- viewBox="0 0 122.88 107.54"
13615
- class="h-6 w-6 shrink-0 rotate-180"
13616
- aria-hidden="true"
13617
- focusable="false"
13877
+ <button
13878
+ type="button"
13879
+ role="radio"
13880
+ [attr.aria-checked]="mode() === 'stn'"
13881
+ aria-label="Shipping to Nigeria"
13882
+ title="Shipping to Nigeria"
13883
+ [class]="cardClass('stn')"
13884
+ (click)="select('stn')"
13618
13885
  >
13619
- <path [attr.d]="glyphPath()" fill="currentColor" />
13620
- </svg>
13621
- @if (!collapsed()) {
13622
- <span class="flex flex-col leading-tight">
13623
- <span>Shipping</span>
13624
- <span>to Nigeria</span>
13625
- </span>
13626
- }
13627
- </button>
13628
- <button
13629
- type="button"
13630
- role="radio"
13631
- [attr.aria-checked]="mode() === 'sfn'"
13632
- aria-label="Shipping from Nigeria"
13633
- [class]="cardClass('sfn')"
13634
- (click)="select('sfn')"
13635
- >
13636
- <svg
13637
- viewBox="0 0 122.88 107.54"
13638
- class="h-6 w-6 shrink-0"
13639
- aria-hidden="true"
13640
- focusable="false"
13886
+ <svg
13887
+ viewBox="0 0 122.88 107.54"
13888
+ class="h-5 w-5 shrink-0 rotate-180"
13889
+ aria-hidden="true"
13890
+ focusable="false"
13891
+ >
13892
+ <path [attr.d]="glyphPath()" fill="currentColor" />
13893
+ </svg>
13894
+ </button>
13895
+ <button
13896
+ type="button"
13897
+ role="radio"
13898
+ [attr.aria-checked]="mode() === 'sfn'"
13899
+ aria-label="Shipping from Nigeria"
13900
+ title="Shipping from Nigeria"
13901
+ [class]="cardClass('sfn')"
13902
+ (click)="select('sfn')"
13641
13903
  >
13642
- <path [attr.d]="glyphPath()" fill="currentColor" />
13643
- </svg>
13644
- @if (!collapsed()) {
13645
- <span class="flex flex-col leading-tight">
13646
- <span>Shipping</span>
13647
- <span>from Nigeria</span>
13648
- </span>
13904
+ <svg
13905
+ viewBox="0 0 122.88 107.54"
13906
+ class="h-5 w-5 shrink-0"
13907
+ aria-hidden="true"
13908
+ focusable="false"
13909
+ >
13910
+ <path [attr.d]="glyphPath()" fill="currentColor" />
13911
+ </svg>
13912
+ </button>
13913
+ </div>
13914
+ } @else {
13915
+ <div class="mb-2 flex items-center gap-1.5">
13916
+ <p
13917
+ class="m-0 shrink-0 px-1 text-caption font-semibold uppercase tracking-wide text-neutral-600 dark:text-neutral-400"
13918
+ >
13919
+ Shipping mode
13920
+ </p>
13921
+ <!-- STN / SFN chip only when the tiles are collapsed — open panel already shows both modes. -->
13922
+ @if (!panelOpen()) {
13923
+ <africanies-chip [variant]="modeChipVariant()" size="sm">
13924
+ {{ modeChipLabel() }}
13925
+ </africanies-chip>
13649
13926
  }
13650
- </button>
13651
- </div>
13927
+ <span class="min-w-0 flex-1" aria-hidden="true"></span>
13928
+ <button
13929
+ type="button"
13930
+ class="inline-flex size-7 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"
13931
+ [attr.aria-label]="
13932
+ panelOpen() ? 'Hide shipping modes' : 'Show shipping modes'
13933
+ "
13934
+ [attr.aria-expanded]="panelOpen()"
13935
+ (click)="togglePanel()"
13936
+ >
13937
+ <africanies-icon
13938
+ [name]="panelOpen() ? 'chevron-up' : 'chevron-down'"
13939
+ [size]="14"
13940
+ />
13941
+ </button>
13942
+ </div>
13943
+
13944
+ <div
13945
+ class="grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none"
13946
+ [style.grid-template-rows]="panelOpen() ? '1fr' : '0fr'"
13947
+ [attr.aria-hidden]="!panelOpen()"
13948
+ [class.pointer-events-none]="!panelOpen()"
13949
+ >
13950
+ <div class="min-h-0 overflow-hidden">
13951
+ <div
13952
+ class="grid grid-cols-2 gap-1.5"
13953
+ role="radiogroup"
13954
+ aria-label="Shipping mode"
13955
+ >
13956
+ <button
13957
+ type="button"
13958
+ role="radio"
13959
+ [attr.aria-checked]="mode() === 'stn'"
13960
+ aria-label="Shipping to Nigeria"
13961
+ [class]="cardClass('stn')"
13962
+ (click)="select('stn')"
13963
+ >
13964
+ <svg
13965
+ viewBox="0 0 122.88 107.54"
13966
+ class="h-6 w-6 shrink-0 rotate-180"
13967
+ aria-hidden="true"
13968
+ focusable="false"
13969
+ >
13970
+ <path [attr.d]="glyphPath()" fill="currentColor" />
13971
+ </svg>
13972
+ <span class="flex flex-col leading-tight">
13973
+ <span>Shipping</span>
13974
+ <span>to Nigeria</span>
13975
+ </span>
13976
+ </button>
13977
+ <button
13978
+ type="button"
13979
+ role="radio"
13980
+ [attr.aria-checked]="mode() === 'sfn'"
13981
+ aria-label="Shipping from Nigeria"
13982
+ [class]="cardClass('sfn')"
13983
+ (click)="select('sfn')"
13984
+ >
13985
+ <svg
13986
+ viewBox="0 0 122.88 107.54"
13987
+ class="h-6 w-6 shrink-0"
13988
+ aria-hidden="true"
13989
+ focusable="false"
13990
+ >
13991
+ <path [attr.d]="glyphPath()" fill="currentColor" />
13992
+ </svg>
13993
+ <span class="flex flex-col leading-tight">
13994
+ <span>Shipping</span>
13995
+ <span>from Nigeria</span>
13996
+ </span>
13997
+ </button>
13998
+ </div>
13999
+ </div>
14000
+ </div>
14001
+ }
13652
14002
  `,
13653
14003
  }]
13654
- }], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }] } });
14004
+ }], ctorParameters: () => [], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], panelOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelOpen", required: false }] }, { type: i0.Output, args: ["panelOpenChange"] }] } });
13655
14005
 
13656
14006
  /**
13657
14007
  * Side-nav shipping mode switcher (`africanies-shipping-mode-switch`).
@@ -13797,7 +14147,10 @@ const DEFAULT_LINK_ACTIVE$1 = {
13797
14147
  * ## Parent items with children
13798
14148
  * **Expanded rail:** branches start open by default (`expandBranchesByDefault`).
13799
14149
  * Clicking a parent only opens/closes the branch (no route change). When any
13800
- * branch is open, a collapse-all control sits above the list (top right).
14150
+ * branch is open, a sticky collapse-all control sits above the list (top right).
14151
+ * The rail expand/collapse control stays pinned in the logo header. Project
14152
+ * chrome above the scrollable list with `pre-nav` (e.g. shipping modes) or
14153
+ * below with `footer`.
13801
14154
  * **Collapsed rail:** clicking a parent expands the rail, opens the
13802
14155
  * branch, and activates the first enabled child (router or `activeId`).
13803
14156
  *
@@ -13910,6 +14263,10 @@ class SideNavComponent {
13910
14263
  collapseAllBranches() {
13911
14264
  this.openBranches.set(new Set());
13912
14265
  }
14266
+ /** Open every parent branch (e.g. so the collapse-all control is visible). */
14267
+ expandAllBranches() {
14268
+ this.openBranches.set(new Set(this.collectParentIds(this.items())));
14269
+ }
13913
14270
  onItemEnter(id) {
13914
14271
  if (this.collapsed()) {
13915
14272
  this.hoverId.set(id);
@@ -13942,7 +14299,7 @@ class SideNavComponent {
13942
14299
  }
13943
14300
  rowClass(item) {
13944
14301
  const base = 'group relative flex w-full items-center gap-2 rounded-lg text-body-sm no-underline transition-colors ' +
13945
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink ' +
14302
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus ' +
13946
14303
  (this.collapsed() ? 'justify-center px-1.5 py-2' : 'px-3 py-2') +
13947
14304
  (item.disabled ? ' cursor-not-allowed opacity-50' : ' cursor-pointer');
13948
14305
  const leaf = this.isLeafActive(item);
@@ -13956,17 +14313,17 @@ class SideNavComponent {
13956
14313
  if (ancestor) {
13957
14314
  return `${base} bg-transparent ${colors.text} font-semibold`;
13958
14315
  }
13959
- return (`${base} font-medium text-neutral-600 hover:bg-background-welcome hover:text-ink ` +
14316
+ return (`${base} font-medium text-neutral-600 hover:bg-background-hover hover:text-ink ` +
13960
14317
  'dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white');
13961
14318
  }
13962
14319
  childRowClass(item) {
13963
14320
  const base = 'relative flex w-full items-center gap-2 rounded-lg px-3 py-1.5 text-body-sm no-underline transition-colors ' +
13964
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink ' +
14321
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus ' +
13965
14322
  (item.disabled ? 'cursor-not-allowed opacity-50 ' : 'cursor-pointer ');
13966
14323
  if (this.isLeafActive(item)) {
13967
14324
  return `${base} ${this.modeColor.classes().activeFill} font-semibold`;
13968
14325
  }
13969
- return (`${base} font-medium text-neutral-600 hover:bg-background-welcome hover:text-ink ` +
14326
+ return (`${base} font-medium text-neutral-600 hover:bg-background-hover hover:text-ink ` +
13970
14327
  'dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white');
13971
14328
  }
13972
14329
  /**
@@ -14109,13 +14466,13 @@ class SideNavComponent {
14109
14466
  return null;
14110
14467
  }
14111
14468
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SideNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14112
- 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-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=\"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-950 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 });
14469
+ 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 <ng-content select=\"[pre-nav]\" />\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-20 h-0 overflow-visible\">\n <button\n type=\"button\"\n class=\"absolute -right-2 -top-1 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-ink-surface 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 });
14113
14470
  }
14114
14471
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SideNavComponent, decorators: [{
14115
14472
  type: Component,
14116
14473
  args: [{ selector: 'africanies-side-nav', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AfricaniesIconComponent, RouterLink, NgTemplateOutlet], host: {
14117
- class: 'relative z-20 block h-full',
14118
- }, 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-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=\"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-950 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"] }]
14474
+ class: 'relative z-20 block h-full min-h-0',
14475
+ }, 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 <ng-content select=\"[pre-nav]\" />\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-20 h-0 overflow-visible\">\n <button\n type=\"button\"\n class=\"absolute -right-2 -top-1 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-ink-surface 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"] }]
14119
14476
  }], 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 }] }] } });
14120
14477
 
14121
14478
  /**
@@ -14644,7 +15001,7 @@ class AppShellContentHeaderComponent {
14644
15001
  <div class="flex min-w-0 items-center gap-2 sm:gap-3">
14645
15002
  @if (showBackButton()) {
14646
15003
  <a
14647
- 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"
15004
+ 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"
14648
15005
  [attr.aria-label]="backLabel()"
14649
15006
  [routerLink]="backTarget()!.routerLink"
14650
15007
  [queryParams]="backTarget()!.queryParams"
@@ -14690,7 +15047,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
14690
15047
  <div class="flex min-w-0 items-center gap-2 sm:gap-3">
14691
15048
  @if (showBackButton()) {
14692
15049
  <a
14693
- 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"
15050
+ 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"
14694
15051
  [attr.aria-label]="backLabel()"
14695
15052
  [routerLink]="backTarget()!.routerLink"
14696
15053
  [queryParams]="backTarget()!.queryParams"
@@ -14855,28 +15212,32 @@ class AvatarMenuComponent {
14855
15212
  showCaret = input(true, { ...(ngDevMode ? { debugName: "showCaret" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
14856
15213
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AvatarMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14857
15214
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AvatarMenuComponent, isStandalone: true, selector: "africanies-avatar-menu", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, menuItems: { classPropertyName: "menuItems", publicName: "menuItems", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showCaret: { classPropertyName: "showCaret", publicName: "showCaret", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
14858
- <africanies-action-menu
14859
- [items]="menuItems()"
14860
- [ariaLabel]="ariaLabel()"
14861
- [disabled]="disabled()"
14862
- >
14863
- <button
14864
- type="button"
14865
- africaniesActionMenuTrigger
14866
- 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"
14867
- [attr.aria-label]="ariaLabel()"
15215
+ @if (menuItems().length > 0) {
15216
+ <africanies-action-menu
15217
+ [items]="menuItems()"
15218
+ [ariaLabel]="ariaLabel()"
15219
+ [disabled]="disabled()"
14868
15220
  >
14869
- <africanies-avatar [name]="name()" [src]="src()" [size]="size()" />
14870
- @if (showCaret()) {
14871
- <africanies-icon
14872
- name="chevron-down"
14873
- [size]="14"
14874
- class="shrink-0 text-neutral-500 dark:text-neutral-400"
14875
- aria-hidden="true"
14876
- />
14877
- }
14878
- </button>
14879
- </africanies-action-menu>
15221
+ <button
15222
+ type="button"
15223
+ africaniesActionMenuTrigger
15224
+ 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"
15225
+ [attr.aria-label]="ariaLabel()"
15226
+ >
15227
+ <africanies-avatar [name]="name()" [src]="src()" [size]="size()" />
15228
+ @if (showCaret()) {
15229
+ <africanies-icon
15230
+ name="chevron-down"
15231
+ [size]="14"
15232
+ class="shrink-0 text-neutral-600 dark:text-neutral-400"
15233
+ aria-hidden="true"
15234
+ />
15235
+ }
15236
+ </button>
15237
+ </africanies-action-menu>
15238
+ } @else {
15239
+ <africanies-avatar [name]="name()" [src]="src()" [size]="size()" />
15240
+ }
14880
15241
  `, isInline: true, dependencies: [{ kind: "component", type: AvatarComponent, selector: "africanies-avatar", inputs: ["name", "src", "alt", "size"] }, { kind: "component", type: ActionMenuComponent, selector: "africanies-action-menu", inputs: ["items", "ariaLabel", "disabled"] }, { kind: "directive", type: ActionMenuTriggerDirective, selector: "[africaniesActionMenuTrigger]" }, { kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14881
15242
  }
14882
15243
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AvatarMenuComponent, decorators: [{
@@ -14892,28 +15253,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
14892
15253
  AfricaniesIconComponent,
14893
15254
  ],
14894
15255
  template: `
14895
- <africanies-action-menu
14896
- [items]="menuItems()"
14897
- [ariaLabel]="ariaLabel()"
14898
- [disabled]="disabled()"
14899
- >
14900
- <button
14901
- type="button"
14902
- africaniesActionMenuTrigger
14903
- 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"
14904
- [attr.aria-label]="ariaLabel()"
15256
+ @if (menuItems().length > 0) {
15257
+ <africanies-action-menu
15258
+ [items]="menuItems()"
15259
+ [ariaLabel]="ariaLabel()"
15260
+ [disabled]="disabled()"
14905
15261
  >
14906
- <africanies-avatar [name]="name()" [src]="src()" [size]="size()" />
14907
- @if (showCaret()) {
14908
- <africanies-icon
14909
- name="chevron-down"
14910
- [size]="14"
14911
- class="shrink-0 text-neutral-500 dark:text-neutral-400"
14912
- aria-hidden="true"
14913
- />
14914
- }
14915
- </button>
14916
- </africanies-action-menu>
15262
+ <button
15263
+ type="button"
15264
+ africaniesActionMenuTrigger
15265
+ 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"
15266
+ [attr.aria-label]="ariaLabel()"
15267
+ >
15268
+ <africanies-avatar [name]="name()" [src]="src()" [size]="size()" />
15269
+ @if (showCaret()) {
15270
+ <africanies-icon
15271
+ name="chevron-down"
15272
+ [size]="14"
15273
+ class="shrink-0 text-neutral-600 dark:text-neutral-400"
15274
+ aria-hidden="true"
15275
+ />
15276
+ }
15277
+ </button>
15278
+ </africanies-action-menu>
15279
+ } @else {
15280
+ <africanies-avatar [name]="name()" [src]="src()" [size]="size()" />
15281
+ }
14917
15282
  `,
14918
15283
  }]
14919
15284
  }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], menuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuItems", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showCaret: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCaret", required: false }] }] } });
@@ -14941,9 +15306,9 @@ class NotificationDrawerPanel {
14941
15306
  viewLinkClass = computed(() => {
14942
15307
  const base = 'inline-flex cursor-pointer items-center gap-1 self-start text-caption font-semibold underline underline-offset-[3px] transition-colors';
14943
15308
  if (this.shipping.mode() === 'stn') {
14944
- return `${base} text-import decoration-import/35 hover:decoration-import dark:text-import-light dark:decoration-import-light/40 dark:hover:decoration-import-light`;
15309
+ 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`;
14945
15310
  }
14946
- return `${base} text-export decoration-export/35 hover:decoration-export dark:text-export-light dark:decoration-export-light/40 dark:hover:decoration-export-light`;
15311
+ 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`;
14947
15312
  }, /* @ts-ignore */
14948
15313
  ...(ngDevMode ? [{ debugName: "viewLinkClass" }] : /* istanbul ignore next */ []));
14949
15314
  title = computed(() => this.data.title ?? 'Notifications', /* @ts-ignore */
@@ -15289,7 +15654,7 @@ class NotificationDrawerPanel {
15289
15654
  {{ title() }}
15290
15655
  </h2>
15291
15656
  @if (hasUnread()) {
15292
- <p class="m-0 text-caption text-neutral-500 dark:text-neutral-400">
15657
+ <p class="m-0 text-caption text-neutral-600 dark:text-neutral-400">
15293
15658
  {{ unreadCount() }} unread
15294
15659
  </p>
15295
15660
  }
@@ -15335,7 +15700,7 @@ class NotificationDrawerPanel {
15335
15700
  <africanies-icon
15336
15701
  name="spinner"
15337
15702
  [size]="24"
15338
- class="animate-spin text-neutral-500 dark:text-neutral-400"
15703
+ class="animate-spin text-neutral-600 dark:text-neutral-400"
15339
15704
  />
15340
15705
  <p class="m-0 text-body-sm text-neutral-600 dark:text-neutral-400">
15341
15706
  Loading notifications…
@@ -15402,7 +15767,7 @@ class NotificationDrawerPanel {
15402
15767
  </h3>
15403
15768
  @if (item.timestamp) {
15404
15769
  <time
15405
- class="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-neutral-500 dark:text-neutral-400"
15770
+ class="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-neutral-600 dark:text-neutral-400"
15406
15771
  [dateTime]="item.timestamp"
15407
15772
  >
15408
15773
  {{ formatTimestamp(item.timestamp) }}
@@ -15481,7 +15846,7 @@ class NotificationDrawerPanel {
15481
15846
  >
15482
15847
  @if (loadingMore()) {
15483
15848
  <span
15484
- class="inline-flex items-center gap-2 text-caption text-neutral-500 dark:text-neutral-400"
15849
+ class="inline-flex items-center gap-2 text-caption text-neutral-600 dark:text-neutral-400"
15485
15850
  aria-live="polite"
15486
15851
  >
15487
15852
  <africanies-icon name="spinner" [size]="14" class="animate-spin" />
@@ -15520,7 +15885,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
15520
15885
  {{ title() }}
15521
15886
  </h2>
15522
15887
  @if (hasUnread()) {
15523
- <p class="m-0 text-caption text-neutral-500 dark:text-neutral-400">
15888
+ <p class="m-0 text-caption text-neutral-600 dark:text-neutral-400">
15524
15889
  {{ unreadCount() }} unread
15525
15890
  </p>
15526
15891
  }
@@ -15566,7 +15931,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
15566
15931
  <africanies-icon
15567
15932
  name="spinner"
15568
15933
  [size]="24"
15569
- class="animate-spin text-neutral-500 dark:text-neutral-400"
15934
+ class="animate-spin text-neutral-600 dark:text-neutral-400"
15570
15935
  />
15571
15936
  <p class="m-0 text-body-sm text-neutral-600 dark:text-neutral-400">
15572
15937
  Loading notifications…
@@ -15633,7 +15998,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
15633
15998
  </h3>
15634
15999
  @if (item.timestamp) {
15635
16000
  <time
15636
- class="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-neutral-500 dark:text-neutral-400"
16001
+ class="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-neutral-600 dark:text-neutral-400"
15637
16002
  [dateTime]="item.timestamp"
15638
16003
  >
15639
16004
  {{ formatTimestamp(item.timestamp) }}
@@ -15712,7 +16077,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
15712
16077
  >
15713
16078
  @if (loadingMore()) {
15714
16079
  <span
15715
- class="inline-flex items-center gap-2 text-caption text-neutral-500 dark:text-neutral-400"
16080
+ class="inline-flex items-center gap-2 text-caption text-neutral-600 dark:text-neutral-400"
15716
16081
  aria-live="polite"
15717
16082
  >
15718
16083
  <africanies-icon name="spinner" [size]="14" class="animate-spin" />
@@ -16016,10 +16381,10 @@ function hashString(value) {
16016
16381
  }
16017
16382
 
16018
16383
  /** Bump when cache shape or city resolution strategy changes. */
16019
- const CACHE_KEY = 'africanies-header-weather-v4';
16384
+ const CACHE_KEY = 'africanies-header-weather-v7';
16020
16385
  const FETCH_MS = 4_000;
16021
16386
  const BROWSER_GEO_MS = 3_500;
16022
- /** IP fallback — city string is often wrong (e.g. NG IPs labelled Lagos). */
16387
+ /** IP fallback — city string is used only when reverse-geocode is empty. */
16023
16388
  const GEO_URL = 'https://get.geojs.io/v1/ip/geo.json';
16024
16389
  /** Client reverse-geocode so the label matches the forecast coordinates. */
16025
16390
  const REVERSE_GEO_URL = 'https://api.bigdatacloud.net/data/reverse-geocode-client';
@@ -16066,9 +16431,13 @@ function mapWmoWeatherCode(code) {
16066
16431
  * Short condition label for the header weather chip.
16067
16432
  *
16068
16433
  * @param kind - Coarse forecast kind.
16069
- * @returns Display label such as `Rain` or `Clear`.
16434
+ * @param hour - Local hour; clear days show as Sunny, nights as Clear.
16435
+ * @returns Display label such as `Rain` or `Sunny`.
16070
16436
  */
16071
- function headerWeatherLabel(kind) {
16437
+ function headerWeatherLabel(kind, hour = new Date().getHours()) {
16438
+ if (kind === 'clear') {
16439
+ return hour >= 19 || hour < 6 ? 'Clear' : 'Sunny';
16440
+ }
16072
16441
  return WEATHER_LABELS[kind];
16073
16442
  }
16074
16443
  /**
@@ -16092,8 +16461,7 @@ function headerWeatherIcon(kind, hour) {
16092
16461
  *
16093
16462
  * Coordinates prefer the browser Geolocation API (accurate for travellers;
16094
16463
  * may prompt once). Otherwise IP geo is used. The city label is reverse-
16095
- * geocoded from those coordinates IP `city` strings are often wrong for
16096
- * Nigeria (many networks resolve as Lagos).
16464
+ * geocoded from those coordinates, with the IP city as a fallback.
16097
16465
  *
16098
16466
  * Fails closed: missing browser APIs, timeouts, and HTTP errors all return
16099
16467
  * `null` so the greeting can stay time-of-day only. Cached per local hour
@@ -16156,10 +16524,16 @@ function readCache(hour) {
16156
16524
  if (parsed.hour !== hour || !parsed.kind) {
16157
16525
  return null;
16158
16526
  }
16527
+ // City-less entries (e.g. browser geo + failed reverse) used to stick for
16528
+ // the hour — treat them as a miss so we can recover the location label.
16529
+ const city = asCity(parsed.city);
16530
+ if (!city) {
16531
+ return null;
16532
+ }
16159
16533
  return {
16160
16534
  kind: parsed.kind,
16161
16535
  temperatureC: parsed.temperatureC,
16162
- city: parsed.city,
16536
+ city,
16163
16537
  };
16164
16538
  }
16165
16539
  catch {
@@ -16176,26 +16550,32 @@ function writeCache(value) {
16176
16550
  }
16177
16551
  /**
16178
16552
  * Prefer device coordinates when available (may prompt once); otherwise fall
16179
- * back to IP geo. IP city labels for Nigeria are often wrong reverse
16180
- * geocoding from these coordinates still depends on accurate lat/lon.
16553
+ * back to IP geo. IP is always fetched in parallel so its city can backfill
16554
+ * the label when reverse-geocode is empty (browser geo has no city string).
16181
16555
  */
16182
16556
  async function resolveCoordinates(fetchFn) {
16183
- const browser = await tryBrowserGeolocation();
16557
+ const [browser, ipGeo] = await Promise.all([
16558
+ tryBrowserGeolocation(),
16559
+ fetchIpGeo(fetchFn),
16560
+ ]);
16561
+ const ipCity = asCity(ipGeo?.city);
16184
16562
  if (browser) {
16185
- return browser;
16563
+ return { ...browser, fallbackCity: ipCity };
16186
16564
  }
16187
- const geo = (await fetchJson(fetchFn, GEO_URL));
16188
- const latitude = asNumber(geo?.latitude);
16189
- const longitude = asNumber(geo?.longitude);
16565
+ const latitude = asNumber(ipGeo?.latitude);
16566
+ const longitude = asNumber(ipGeo?.longitude);
16190
16567
  if (latitude === null || longitude === null) {
16191
16568
  return null;
16192
16569
  }
16193
16570
  return {
16194
16571
  latitude,
16195
16572
  longitude,
16196
- fallbackCity: asCity(geo?.city),
16573
+ fallbackCity: ipCity,
16197
16574
  };
16198
16575
  }
16576
+ async function fetchIpGeo(fetchFn) {
16577
+ return (await fetchJson(fetchFn, GEO_URL));
16578
+ }
16199
16579
  async function tryBrowserGeolocation() {
16200
16580
  if (typeof navigator === 'undefined' ||
16201
16581
  typeof navigator.geolocation?.getCurrentPosition !== 'function') {
@@ -16380,26 +16760,33 @@ class AppShellHeaderComponent {
16380
16760
  if (temp === undefined || !Number.isFinite(temp)) {
16381
16761
  return null;
16382
16762
  }
16383
- return temp.toFixed(1);
16763
+ return String(Math.round(temp));
16384
16764
  }, /* @ts-ignore */
16385
16765
  ...(ngDevMode ? [{ debugName: "weatherTemp" }] : /* istanbul ignore next */ []));
16386
- weatherPlace = computed(() => {
16766
+ weatherCondition = computed(() => {
16387
16767
  const forecast = this.weather();
16388
16768
  if (!forecast) {
16389
16769
  return '';
16390
16770
  }
16391
- return forecast.city?.trim() || headerWeatherLabel(forecast.kind);
16771
+ return headerWeatherLabel(forecast.kind, this.now().getHours());
16392
16772
  }, /* @ts-ignore */
16393
- ...(ngDevMode ? [{ debugName: "weatherPlace" }] : /* istanbul ignore next */ []));
16773
+ ...(ngDevMode ? [{ debugName: "weatherCondition" }] : /* istanbul ignore next */ []));
16774
+ weatherCity = computed(() => {
16775
+ const city = this.weather()?.city?.trim();
16776
+ return city || null;
16777
+ }, /* @ts-ignore */
16778
+ ...(ngDevMode ? [{ debugName: "weatherCity" }] : /* istanbul ignore next */ []));
16394
16779
  weatherAriaLabel = computed(() => {
16395
16780
  const forecast = this.weather();
16396
16781
  if (!forecast) {
16397
16782
  return 'Weather';
16398
16783
  }
16399
- const condition = headerWeatherLabel(forecast.kind).toLowerCase();
16784
+ const condition = this.weatherCondition().toLowerCase();
16400
16785
  const temp = this.weatherTemp();
16401
- const city = forecast.city?.trim();
16402
- const degrees = temp === null ? condition : `${temp} degrees, ${condition}`;
16786
+ const city = this.weatherCity();
16787
+ const degrees = temp === null
16788
+ ? condition
16789
+ : `${temp} degrees Celsius, ${condition}`;
16403
16790
  return city ? `Weather in ${city}: ${degrees}` : `Weather: ${degrees}`;
16404
16791
  }, /* @ts-ignore */
16405
16792
  ...(ngDevMode ? [{ debugName: "weatherAriaLabel" }] : /* istanbul ignore next */ []));
@@ -16469,7 +16856,7 @@ class AppShellHeaderComponent {
16469
16856
  @if (greeting(); as hello) {
16470
16857
  <div class="min-w-0 overflow-hidden">
16471
16858
  <p
16472
- class="m-0 truncate text-caption leading-none text-neutral-500 dark:text-neutral-400"
16859
+ class="m-0 truncate text-caption leading-none text-neutral-600 dark:text-neutral-400"
16473
16860
  >
16474
16861
  {{ hello.kicker }}
16475
16862
  </p>
@@ -16495,19 +16882,21 @@ class AppShellHeaderComponent {
16495
16882
  <africanies-icon
16496
16883
  [name]="weatherIcon()"
16497
16884
  [size]="18"
16498
- class="text-neutral-500 dark:text-neutral-400"
16885
+ class="text-neutral-600 dark:text-neutral-400"
16499
16886
  />
16500
16887
  @if (weatherTemp(); as temp) {
16501
16888
  <span
16502
16889
  class="text-body-sm font-medium tabular-nums tracking-tight text-ink dark:text-white"
16503
16890
  >
16504
- {{ temp }}°
16891
+ {{ temp }}°C
16505
16892
  </span>
16506
16893
  }
16507
16894
  <span
16508
- class="hidden text-caption text-neutral-500 md:inline dark:text-neutral-400"
16895
+ class="hidden text-caption text-neutral-600 md:inline dark:text-neutral-400"
16509
16896
  >
16510
- {{ weatherPlace() }}
16897
+ {{ weatherCondition() }}@if (weatherCity(); as city) {
16898
+ · {{ city }}
16899
+ }
16511
16900
  </span>
16512
16901
  </div>
16513
16902
  }
@@ -16515,7 +16904,7 @@ class AppShellHeaderComponent {
16515
16904
  @if (showClock()) {
16516
16905
  <div class="flex items-baseline gap-2" aria-live="polite">
16517
16906
  <time
16518
- class="hidden text-caption text-neutral-500 md:inline dark:text-neutral-400"
16907
+ class="hidden text-caption text-neutral-600 md:inline dark:text-neutral-400"
16519
16908
  [dateTime]="nowIso()"
16520
16909
  >
16521
16910
  {{ now() | date: clockDateFormat() }}
@@ -16545,7 +16934,7 @@ class AppShellHeaderComponent {
16545
16934
  @if (showNotifications()) {
16546
16935
  <button
16547
16936
  type="button"
16548
- 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"
16937
+ 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"
16549
16938
  [attr.aria-label]="notificationAriaLabel()"
16550
16939
  (click)="openNotifications()"
16551
16940
  >
@@ -16566,18 +16955,26 @@ class AppShellHeaderComponent {
16566
16955
  }
16567
16956
 
16568
16957
  @if (userName()) {
16569
- <africanies-avatar-menu
16570
- [name]="userName()!"
16571
- [src]="userAvatarSrc()"
16572
- [menuItems]="userMenuItems()"
16573
- [size]="'md'"
16574
- />
16958
+ @if (userMenuItems().length > 0) {
16959
+ <africanies-avatar-menu
16960
+ [name]="userName()!"
16961
+ [src]="userAvatarSrc()"
16962
+ [menuItems]="userMenuItems()"
16963
+ [size]="'md'"
16964
+ />
16965
+ } @else {
16966
+ <africanies-avatar
16967
+ [name]="userName()!"
16968
+ [src]="userAvatarSrc()"
16969
+ size="md"
16970
+ />
16971
+ }
16575
16972
  }
16576
16973
  </div>
16577
16974
  }
16578
16975
  </div>
16579
16976
  </div>
16580
- `, isInline: true, dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: AvatarMenuComponent, selector: "africanies-avatar-menu", inputs: ["name", "src", "menuItems", "size", "ariaLabel", "disabled", "showCaret"] }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16977
+ `, isInline: true, dependencies: [{ kind: "component", type: AfricaniesIconComponent, selector: "africanies-icon", inputs: ["name", "size"] }, { kind: "component", type: AvatarComponent, selector: "africanies-avatar", inputs: ["name", "src", "alt", "size"] }, { kind: "component", type: AvatarMenuComponent, selector: "africanies-avatar-menu", inputs: ["name", "src", "menuItems", "size", "ariaLabel", "disabled", "showCaret"] }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16581
16978
  }
16582
16979
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AppShellHeaderComponent, decorators: [{
16583
16980
  type: Component,
@@ -16585,7 +16982,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16585
16982
  selector: 'africanies-app-shell-header',
16586
16983
  standalone: true,
16587
16984
  changeDetection: ChangeDetectionStrategy.OnPush,
16588
- imports: [AfricaniesIconComponent, AvatarMenuComponent, DatePipe],
16985
+ imports: [
16986
+ AfricaniesIconComponent,
16987
+ AvatarComponent,
16988
+ AvatarMenuComponent,
16989
+ DatePipe,
16990
+ ],
16589
16991
  template: `
16590
16992
  <div
16591
16993
  class="flex min-w-0 items-center gap-2 px-3 py-2.5 sm:gap-4 sm:px-6 sm:py-3"
@@ -16596,7 +16998,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16596
16998
  @if (greeting(); as hello) {
16597
16999
  <div class="min-w-0 overflow-hidden">
16598
17000
  <p
16599
- class="m-0 truncate text-caption leading-none text-neutral-500 dark:text-neutral-400"
17001
+ class="m-0 truncate text-caption leading-none text-neutral-600 dark:text-neutral-400"
16600
17002
  >
16601
17003
  {{ hello.kicker }}
16602
17004
  </p>
@@ -16622,19 +17024,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16622
17024
  <africanies-icon
16623
17025
  [name]="weatherIcon()"
16624
17026
  [size]="18"
16625
- class="text-neutral-500 dark:text-neutral-400"
17027
+ class="text-neutral-600 dark:text-neutral-400"
16626
17028
  />
16627
17029
  @if (weatherTemp(); as temp) {
16628
17030
  <span
16629
17031
  class="text-body-sm font-medium tabular-nums tracking-tight text-ink dark:text-white"
16630
17032
  >
16631
- {{ temp }}°
17033
+ {{ temp }}°C
16632
17034
  </span>
16633
17035
  }
16634
17036
  <span
16635
- class="hidden text-caption text-neutral-500 md:inline dark:text-neutral-400"
17037
+ class="hidden text-caption text-neutral-600 md:inline dark:text-neutral-400"
16636
17038
  >
16637
- {{ weatherPlace() }}
17039
+ {{ weatherCondition() }}@if (weatherCity(); as city) {
17040
+ · {{ city }}
17041
+ }
16638
17042
  </span>
16639
17043
  </div>
16640
17044
  }
@@ -16642,7 +17046,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16642
17046
  @if (showClock()) {
16643
17047
  <div class="flex items-baseline gap-2" aria-live="polite">
16644
17048
  <time
16645
- class="hidden text-caption text-neutral-500 md:inline dark:text-neutral-400"
17049
+ class="hidden text-caption text-neutral-600 md:inline dark:text-neutral-400"
16646
17050
  [dateTime]="nowIso()"
16647
17051
  >
16648
17052
  {{ now() | date: clockDateFormat() }}
@@ -16672,7 +17076,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16672
17076
  @if (showNotifications()) {
16673
17077
  <button
16674
17078
  type="button"
16675
- 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"
17079
+ 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"
16676
17080
  [attr.aria-label]="notificationAriaLabel()"
16677
17081
  (click)="openNotifications()"
16678
17082
  >
@@ -16693,12 +17097,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16693
17097
  }
16694
17098
 
16695
17099
  @if (userName()) {
16696
- <africanies-avatar-menu
16697
- [name]="userName()!"
16698
- [src]="userAvatarSrc()"
16699
- [menuItems]="userMenuItems()"
16700
- [size]="'md'"
16701
- />
17100
+ @if (userMenuItems().length > 0) {
17101
+ <africanies-avatar-menu
17102
+ [name]="userName()!"
17103
+ [src]="userAvatarSrc()"
17104
+ [menuItems]="userMenuItems()"
17105
+ [size]="'md'"
17106
+ />
17107
+ } @else {
17108
+ <africanies-avatar
17109
+ [name]="userName()!"
17110
+ [src]="userAvatarSrc()"
17111
+ size="md"
17112
+ />
17113
+ }
16702
17114
  }
16703
17115
  </div>
16704
17116
  }
@@ -16874,14 +17286,14 @@ class AppShellComponent {
16874
17286
  // Preview embeds: absolute/h-full so the drawer stays inside the frame.
16875
17287
  // Production: h-dvh so the rail fills the viewport (h-full only matches parent).
16876
17288
  if (preview === 'desktop') {
16877
- return 'z-50 shrink-0 self-start sticky top-0 block h-full';
17289
+ return 'z-50 shrink-0 self-start sticky top-0 block h-full min-h-0';
16878
17290
  }
16879
17291
  if (preview === 'mobile' || preview === 'tablet') {
16880
17292
  return open
16881
- ? 'z-50 shrink-0 self-start absolute inset-y-0 left-0 h-full shadow-xl'
17293
+ ? 'z-50 shrink-0 self-start absolute inset-y-0 left-0 h-full min-h-0 shadow-xl'
16882
17294
  : 'hidden';
16883
17295
  }
16884
- const base = 'z-50 shrink-0 self-start h-dvh';
17296
+ const base = 'z-50 shrink-0 self-start h-dvh min-h-0';
16885
17297
  if (open) {
16886
17298
  return `${base} fixed inset-y-0 left-0 shadow-xl lg:sticky lg:top-0 lg:block`;
16887
17299
  }
@@ -16917,13 +17329,13 @@ class AppShellComponent {
16917
17329
  this.mobileNavOpen.set(false);
16918
17330
  }
16919
17331
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AppShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16920
- 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 });
17332
+ 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 });
16921
17333
  }
16922
17334
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AppShellComponent, decorators: [{
16923
17335
  type: Component,
16924
17336
  args: [{ selector: 'africanies-app-shell', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AfricaniesIconComponent, AppShellContentHeaderComponent, AppShellHeaderComponent], host: {
16925
17337
  '[class]': 'hostClass()',
16926
- }, 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>" }]
17338
+ }, 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>" }]
16927
17339
  }], 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 }] }] } });
16928
17340
 
16929
17341
  /**
@@ -17051,7 +17463,7 @@ class ImageComponent {
17051
17463
 
17052
17464
  @if (showPlaceholder()) {
17053
17465
  <div
17054
- class="absolute inset-0 flex items-center justify-center bg-neutral-200 text-neutral-500 dark:bg-white/10 dark:text-neutral-400"
17466
+ class="absolute inset-0 flex items-center justify-center bg-neutral-200 text-neutral-600 dark:bg-white/10 dark:text-neutral-400"
17055
17467
  [class.animate-pulse]="pending()"
17056
17468
  aria-hidden="true"
17057
17469
  >
@@ -17107,7 +17519,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17107
17519
 
17108
17520
  @if (showPlaceholder()) {
17109
17521
  <div
17110
- class="absolute inset-0 flex items-center justify-center bg-neutral-200 text-neutral-500 dark:bg-white/10 dark:text-neutral-400"
17522
+ class="absolute inset-0 flex items-center justify-center bg-neutral-200 text-neutral-600 dark:bg-white/10 dark:text-neutral-400"
17111
17523
  [class.animate-pulse]="pending()"
17112
17524
  aria-hidden="true"
17113
17525
  >
@@ -17461,7 +17873,7 @@ class PaginationComponent {
17461
17873
  @for (item of pageItems(); track trackPageItem(item, $index)) {
17462
17874
  @if (item === 'ellipsis') {
17463
17875
  <span
17464
- class="inline-flex min-w-8 items-center justify-center text-body-sm text-neutral-500 dark:text-neutral-400"
17876
+ class="inline-flex min-w-8 items-center justify-center text-body-sm text-neutral-600 dark:text-neutral-400"
17465
17877
  aria-hidden="true"
17466
17878
  >
17467
17879
 
@@ -17562,7 +17974,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17562
17974
  @for (item of pageItems(); track trackPageItem(item, $index)) {
17563
17975
  @if (item === 'ellipsis') {
17564
17976
  <span
17565
- class="inline-flex min-w-8 items-center justify-center text-body-sm text-neutral-500 dark:text-neutral-400"
17977
+ class="inline-flex min-w-8 items-center justify-center text-body-sm text-neutral-600 dark:text-neutral-400"
17566
17978
  aria-hidden="true"
17567
17979
  >
17568
17980
 
@@ -17608,15 +18020,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17608
18020
  * `<ng-template africaniesCellDef="key">` templates. Columns without a matching
17609
18021
  * template fall back to rendering `row[key]` as plain text.
17610
18022
  *
17611
- * Optional toolbar: top-left **Refresh** (`showRefresh` {@link refreshClick}).
17612
- * Hidden while the body shows empty or error those states expose Retry instead.
18023
+ * Optional toolbar: top-left **shipping mode** segment on its own row above
18024
+ * Refresh / Filter / Export (on by default; `[showShippingMode]="false"` to
18025
+ * hide) and **Refresh** (`showRefresh` →
18026
+ * {@link refreshClick}). Refresh is hidden while
18027
+ * the body shows empty or error — those states expose Retry instead.
17613
18028
  * While {@link refreshing} is true the rows stay on screen — the refresh icon
17614
18029
  * spins (do not swap to a blocking loader). Use {@link loading} when the page
17615
- * of data is changing (pagination / size): rows stay on screen and the built-in
17616
- * pager shows a circular spinner beside the page controls; the body loader
17617
- * appears only when there are no rows yet (first load) or the host cleared
17618
- * rows for a shipping-mode switch (STN SFN). Toolbar and column headers stay
17619
- * mounted.
18030
+ * of data is changing (pagination / size): rows stay on screen, a keep-rows
18031
+ * overlay covers the grid, and the built-in pager shows a circular spinner
18032
+ * beside the page controls; the body loader appears only when there are no
18033
+ * rows yet (first load) or the host cleared rows for a shipping-mode switch
18034
+ * (STN ↔ SFN). Toolbar and column headers stay mounted.
17620
18035
  * top-right **Filters** then **Export** (`showFilter` / `showExport`). The table
17621
18036
  * does not own fetch, filter, or export logic — the host handles those events.
17622
18037
  *
@@ -17638,8 +18053,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17638
18053
  * and never reorder local rows.
17639
18054
  *
17640
18055
  * **Expandable rows:** project `<ng-template africaniesRowDetail="Label" let-row>`
17641
- * templates. A leading chevron column appears; the expanded panel renders each
17642
- * label with its template value in a responsive grid.
18056
+ * templates. A leading chevron column appears; the expanded panel sticks to the
18057
+ * scrollport width (so it does not scroll horizontally with the columns) and
18058
+ * lays out label/value pairs in a wrapping auto-fill grid.
17643
18059
  *
17644
18060
  * @typeParam T - Row record shape.
17645
18061
  *
@@ -17675,9 +18091,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17675
18091
  * to the left when rows are expandable. Override with
17676
18092
  * {@link TableColumn.sticky}. Sticky columns use a slightly thicker bottom
17677
18093
  * border (and opaque fill) so dividers stay visible while scrolling.
18094
+ *
18095
+ * **Sticky header (desktop):** when {@link stickyHeader} is true (default),
18096
+ * large screens scroll the grid inside a capped viewport and keep `th` pinned
18097
+ * to the top of that scrollport. Mobile keeps horizontal-only overflow so
18098
+ * page scroll is unchanged.
17678
18099
  */
17679
18100
  class TableComponent {
17680
- modeColor = inject(ModeColorService);
18101
+ destroyRef = inject(DestroyRef);
18102
+ shipping = inject(ShippingModeService);
18103
+ /** Local STN / SFN segment options for {@link showShippingMode}. */
18104
+ shippingModeItems = [
18105
+ { id: 'stn', label: 'Shipping to Nigeria' },
18106
+ { id: 'sfn', label: 'Shipping from Nigeria' },
18107
+ ];
18108
+ /**
18109
+ * Segment selection mirror of {@link ShippingModeService.mode}. Kept local
18110
+ * so a denied {@link ShippingModeService.requestModeChange} can snap the
18111
+ * pills back when the service mode did not change.
18112
+ */
18113
+ shippingModeActiveId = signal('sfn', /* @ts-ignore */
18114
+ ...(ngDevMode ? [{ debugName: "shippingModeActiveId" }] : /* istanbul ignore next */ []));
18115
+ /** Scrollport that owns horizontal overflow for the main grid. */
18116
+ scrollport = viewChild('scrollport', /* @ts-ignore */
18117
+ ...(ngDevMode ? [{ debugName: "scrollport" }] : /* istanbul ignore next */ []));
18118
+ /**
18119
+ * Visible width of {@link scrollport}. Applied to the expand panel so
18120
+ * details stay within the viewport and do not scroll with the columns.
18121
+ */
18122
+ scrollportWidthPx = signal(0, /* @ts-ignore */
18123
+ ...(ngDevMode ? [{ debugName: "scrollportWidthPx" }] : /* istanbul ignore next */ []));
18124
+ constructor() {
18125
+ effect(() => {
18126
+ this.shippingModeActiveId.set(this.shipping.mode());
18127
+ });
18128
+ afterNextRender(() => {
18129
+ const el = this.scrollport()?.nativeElement;
18130
+ if (!el) {
18131
+ return;
18132
+ }
18133
+ const sync = () => {
18134
+ this.scrollportWidthPx.set(el.clientWidth);
18135
+ };
18136
+ sync();
18137
+ const observer = new ResizeObserver(sync);
18138
+ observer.observe(el);
18139
+ this.destroyRef.onDestroy(() => observer.disconnect());
18140
+ });
18141
+ }
17681
18142
  /**
17682
18143
  * When false, hides row expansion even if {@link RowDetailDefDirective}
17683
18144
  * templates are projected. Defaults to true.
@@ -17693,6 +18154,12 @@ class TableComponent {
17693
18154
  /** Column definitions (header chrome + keys). */
17694
18155
  columns = input.required(/* @ts-ignore */
17695
18156
  ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
18157
+ /**
18158
+ * When true (default), pin column headers while scrolling the grid on
18159
+ * large screens (`lg+`). Uses a capped scrollport so sticky works with
18160
+ * horizontal overflow. Set false to keep the previous unconstrained layout.
18161
+ */
18162
+ stickyHeader = input(true, { ...(ngDevMode ? { debugName: "stickyHeader" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
17696
18163
  /**
17697
18164
  * Row records to render. Empty arrays show {@link EmptyStateComponent} in
17698
18165
  * the body unless {@link loading} or {@link error} is set. Missing `data`
@@ -17706,6 +18173,13 @@ class TableComponent {
17706
18173
  */
17707
18174
  sort = input(null, /* @ts-ignore */
17708
18175
  ...(ngDevMode ? [{ debugName: "sort" }] : /* istanbul ignore next */ []));
18176
+ /**
18177
+ * Show the Shipping to / from Nigeria segment above the table (top-left).
18178
+ * On by default; set `[showShippingMode]="false"` to hide (e.g. modals).
18179
+ * Writes through {@link ShippingModeService.requestModeChange} so feature
18180
+ * guards still run.
18181
+ */
18182
+ showShippingMode = input(true, { ...(ngDevMode ? { debugName: "showShippingMode" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
17709
18183
  /**
17710
18184
  * Show a Refresh button above the table (top-left) once rows are on screen.
17711
18185
  * Hidden during in-grid empty/error — those use Retry in the body. Host refetches
@@ -17723,9 +18197,9 @@ class TableComponent {
17723
18197
  refreshing = input(false, { ...(ngDevMode ? { debugName: "refreshing" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
17724
18198
  /**
17725
18199
  * Page of data is loading (first load, shipping-mode switch, pagination,
17726
- * page size). When rows are already on screen, the pager shows a spinner
17727
- * and rows stay visible; when there are no rows (first load or the host
17728
- * dropped STN/SFN rows after a mode switch), the body shows
18200
+ * page size). When rows are already on screen, a keep-rows overlay covers
18201
+ * the grid and the pager shows a spinner; when there are no rows (first
18202
+ * load or the host dropped STN/SFN rows after a mode switch), the body shows
17729
18203
  * {@link LoadingStateComponent}. Distinct from {@link refreshing}.
17730
18204
  */
17731
18205
  loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
@@ -17837,6 +18311,26 @@ class TableComponent {
17837
18311
  /** Top-left Refresh — only when rows are on screen (empty/error use in-body Retry). */
17838
18312
  showToolbarRefresh = computed(() => this.showRefresh() && this.bodyKind() === 'rows', /* @ts-ignore */
17839
18313
  ...(ngDevMode ? [{ debugName: "showToolbarRefresh" }] : /* istanbul ignore next */ []));
18314
+ /**
18315
+ * Applies a shipping-mode segment selection through
18316
+ * {@link ShippingModeService.requestModeChange}.
18317
+ *
18318
+ * @param id - Segment id (`stn` / `sfn`) or null.
18319
+ */
18320
+ onShippingModeChange(id) {
18321
+ if (id !== 'stn' && id !== 'sfn') {
18322
+ return;
18323
+ }
18324
+ this.shippingModeActiveId.set(id);
18325
+ if (id === this.shipping.mode()) {
18326
+ return;
18327
+ }
18328
+ this.shipping.requestModeChange(id).subscribe((ok) => {
18329
+ if (!ok) {
18330
+ this.shippingModeActiveId.set(this.shipping.mode());
18331
+ }
18332
+ });
18333
+ }
17840
18334
  visibleRowIds = computed(() => this.rowList().map((row, index) => this.rowId(row, index)), /* @ts-ignore */
17841
18335
  ...(ngDevMode ? [{ debugName: "visibleRowIds" }] : /* istanbul ignore next */ []));
17842
18336
  someRowsExpanded = computed(() => {
@@ -17896,11 +18390,12 @@ class TableComponent {
17896
18390
  return 'border-b border-border dark:border-white/10';
17897
18391
  }
17898
18392
  /**
17899
- * Opaque mode soft fill for header cells (sticky-safe in dark mode).
17900
- * @returns Soft solid background utilities.
18393
+ * Opaque header fill (sticky-safe). Neutral chrome — not mode-accented.
18394
+ * Light `#f0f2f5` / dark elevated ink surface.
18395
+ * @returns Background utility classes.
17901
18396
  */
17902
18397
  headerSoftClass() {
17903
- return this.modeColor.classes().softSolid;
18398
+ return 'bg-[#f0f2f5] dark:bg-[#2a2c31]';
17904
18399
  }
17905
18400
  /**
17906
18401
  * Body row classes — row marker for hover styles, plus hide cell borders on
@@ -17971,10 +18466,21 @@ class TableComponent {
17971
18466
  ? 'border-b-[1.5px] '
17972
18467
  : 'border-b ';
17973
18468
  return (border +
17974
- '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 ' +
18469
+ 'border-border whitespace-normal wrap-break-word px-[1.125rem] py-4 font-medium text-neutral-500 dark:border-white/10 dark:text-neutral-400 ' +
17975
18470
  this.headerSoftClass() +
17976
18471
  ' ' +
17977
- this.stickySurfaceClass(col, 'head'));
18472
+ this.stickySurfaceClass(col, 'head') +
18473
+ (sticky ? '' : this.headerStickClass()));
18474
+ }
18475
+ /**
18476
+ * Vertical pin for scrolling header cells on desktop. Pinned left/right
18477
+ * headers get `lg:top-0` from {@link stickyPinClass} instead.
18478
+ */
18479
+ headerStickClass() {
18480
+ if (!this.stickyHeader()) {
18481
+ return '';
18482
+ }
18483
+ return ' lg:sticky lg:top-0 lg:z-[3] print:static';
17978
18484
  }
17979
18485
  /**
17980
18486
  * Body cell classes, including sticky pin when set.
@@ -18013,9 +18519,13 @@ class TableComponent {
18013
18519
  const side = edge === 'right' ? 'sticky right-0 z-[1]' : 'sticky left-0 z-[1]';
18014
18520
  if (surface === 'head') {
18015
18521
  // Fill comes from {@link headerSoftClass} on the cell — do not re-tint.
18016
- return `${side} z-[2]`;
18522
+ // Corner pins also stick vertically on desktop when stickyHeader is on.
18523
+ const vertical = this.stickyHeader()
18524
+ ? ' z-[2] lg:top-0 lg:z-[4] print:static'
18525
+ : ' z-[2]';
18526
+ return `${side}${vertical}`;
18017
18527
  }
18018
- const fill = 'bg-white dark:bg-ink-surface';
18528
+ const fill = 'bg-surface dark:bg-ink-surface';
18019
18529
  return `${side} ${fill}`;
18020
18530
  }
18021
18531
  /**
@@ -18121,89 +18631,112 @@ class TableComponent {
18121
18631
  this.sortChange.emit({ key, direction });
18122
18632
  }
18123
18633
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18124
- 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: `
18634
+ 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 }, showShippingMode: { classPropertyName: "showShippingMode", publicName: "showShippingMode", 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 }], viewQueries: [{ propertyName: "scrollport", first: true, predicate: ["scrollport"], descendants: true, isSignal: true }], ngImport: i0, template: `
18125
18635
  <div
18126
18636
  class="flex w-full min-w-0 flex-col gap-3"
18127
18637
  [attr.aria-busy]="loading() || refreshing() || null"
18128
18638
  >
18129
- @if (showRefresh() || showFilter() || showExport()) {
18130
- <div class="flex items-center justify-between gap-2">
18131
- <div class="flex items-center gap-2">
18132
- @if (showToolbarRefresh()) {
18133
- <button
18134
- africanies-button
18135
- type="button"
18136
- variant="flat"
18137
- size="sm"
18138
- [disabled]="loading() || refreshing()"
18139
- [attr.aria-label]="refreshLabel()"
18140
- (click)="refreshClick.emit()"
18141
- >
18142
- <africanies-icon
18143
- name="refresh"
18144
- [size]="16"
18145
- [class]="refreshing() ? 'animate-spin' : ''"
18146
- />
18147
- {{ refreshLabel() }}
18148
- </button>
18149
- }
18150
- </div>
18151
- <div class="flex items-center gap-2">
18152
- @if (showFilter()) {
18153
- <button
18154
- africanies-button
18155
- type="button"
18156
- variant="flat"
18157
- size="sm"
18158
- [attr.aria-label]="filterLabel()"
18159
- (click)="filterClick.emit()"
18160
- >
18161
- <africanies-icon name="filter" [size]="16" />
18162
- {{ filterLabel() }}
18163
- @if (filterCount() > 0) {
18164
- <span
18165
- class="inline-flex min-w-5 items-center justify-center rounded-full bg-ink px-1.5 py-0.5 text-caption font-semibold tabular-nums text-white dark:bg-white dark:text-ink"
18639
+ @if (
18640
+ showShippingMode() ||
18641
+ showRefresh() ||
18642
+ showFilter() ||
18643
+ showExport()
18644
+ ) {
18645
+ <div class="flex w-full min-w-0 flex-col gap-3">
18646
+ @if (showShippingMode()) {
18647
+ <africanies-segment
18648
+ class="min-w-0 self-start"
18649
+ [items]="shippingModeItems"
18650
+ [activeId]="shippingModeActiveId()"
18651
+ ariaLabel="Shipping mode"
18652
+ (activeIdChange)="onShippingModeChange($event)"
18653
+ />
18654
+ }
18655
+ @if (showToolbarRefresh() || showFilter() || showExport()) {
18656
+ <div class="flex flex-wrap items-center justify-between gap-2">
18657
+ <div class="flex min-w-0 flex-wrap items-center gap-2">
18658
+ @if (showToolbarRefresh()) {
18659
+ <button
18660
+ africanies-button
18661
+ type="button"
18662
+ variant="flat"
18663
+ size="sm"
18664
+ [disabled]="loading() || refreshing()"
18665
+ [attr.aria-label]="refreshLabel()"
18666
+ (click)="refreshClick.emit()"
18166
18667
  >
18167
- {{ filterCount() }}
18168
- </span>
18668
+ <africanies-icon
18669
+ name="refresh"
18670
+ [size]="16"
18671
+ [class]="refreshing() ? 'animate-spin' : ''"
18672
+ />
18673
+ {{ refreshLabel() }}
18674
+ </button>
18169
18675
  }
18170
- </button>
18171
- }
18172
- @if (showExport()) {
18173
- <button
18174
- africanies-button
18175
- type="button"
18176
- variant="flat"
18177
- size="sm"
18178
- [attr.aria-label]="exportLabel()"
18179
- (click)="exportClick.emit()"
18180
- >
18181
- <africanies-icon name="download" [size]="16" />
18182
- {{ exportLabel() }}
18183
- </button>
18184
- }
18185
- </div>
18676
+ </div>
18677
+ <div class="flex items-center gap-2">
18678
+ @if (showFilter()) {
18679
+ <button
18680
+ africanies-button
18681
+ type="button"
18682
+ variant="flat"
18683
+ size="sm"
18684
+ [attr.aria-label]="filterLabel()"
18685
+ (click)="filterClick.emit()"
18686
+ >
18687
+ <africanies-icon name="filter" [size]="16" />
18688
+ {{ filterLabel() }}
18689
+ @if (filterCount() > 0) {
18690
+ <span
18691
+ class="inline-flex min-w-5 items-center justify-center rounded-full bg-ink px-1.5 py-0.5 text-caption font-semibold tabular-nums text-white dark:bg-white dark:text-ink"
18692
+ >
18693
+ {{ filterCount() }}
18694
+ </span>
18695
+ }
18696
+ </button>
18697
+ }
18698
+ @if (showExport()) {
18699
+ <button
18700
+ africanies-button
18701
+ type="button"
18702
+ variant="flat"
18703
+ size="sm"
18704
+ [attr.aria-label]="exportLabel()"
18705
+ (click)="exportClick.emit()"
18706
+ >
18707
+ <africanies-icon name="download" [size]="16" />
18708
+ {{ exportLabel() }}
18709
+ </button>
18710
+ }
18711
+ </div>
18712
+ </div>
18713
+ }
18186
18714
  </div>
18187
18715
  }
18188
18716
 
18189
18717
  @if (staleError(); as staleMessage) {
18190
- <div class="flex justify-end">
18191
- <africanies-error-indicator
18192
- class="max-w-[min(100%,20rem)]"
18193
- [error]="staleMessage"
18194
- retryText="Refresh"
18195
- [refreshing]="refreshing()"
18196
- (retry)="refreshClick.emit()"
18197
- />
18198
- </div>
18718
+ <africanies-error-indicator
18719
+ class="w-full"
18720
+ [error]="staleMessage"
18721
+ retryText="Refresh"
18722
+ [refreshing]="refreshing()"
18723
+ (retry)="refreshClick.emit()"
18724
+ />
18199
18725
  }
18200
18726
 
18201
18727
  <div
18202
- class="min-w-0 w-full overflow-x-auto rounded-md border border-border bg-white dark:border-white/10 dark:bg-ink-surface"
18728
+ class="relative min-w-0 w-full overflow-hidden rounded-panel border border-border bg-surface dark:border-white/10 dark:bg-ink-surface"
18203
18729
  >
18204
- <table
18205
- class="w-max min-w-full table-auto border-separate border-spacing-0 bg-inherit text-left text-body text-ink dark:text-white"
18730
+ <div
18731
+ #scrollport
18732
+ class="africanies-table-scrollport min-w-0 w-full overflow-x-auto"
18733
+ [class.lg:max-h-[min(70dvh,calc(100dvh-12rem))]]="stickyHeader()"
18734
+ [class.lg:overflow-auto]="stickyHeader() && !paginationLoading()"
18735
+ [class.pointer-events-none]="paginationLoading()"
18206
18736
  >
18737
+ <table
18738
+ class="w-max min-w-full table-auto border-separate border-spacing-0 bg-inherit text-left text-body text-ink dark:text-white"
18739
+ >
18207
18740
  <thead [class]="headerRowClass()">
18208
18741
  <tr>
18209
18742
  @if (isExpandable()) {
@@ -18214,7 +18747,7 @@ class TableComponent {
18214
18747
  @if (someRowsExpanded()) {
18215
18748
  <button
18216
18749
  type="button"
18217
- 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"
18750
+ class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-500 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"
18218
18751
  aria-label="Collapse all"
18219
18752
  (click)="collapseAllRows()"
18220
18753
  >
@@ -18223,7 +18756,7 @@ class TableComponent {
18223
18756
  } @else {
18224
18757
  <button
18225
18758
  type="button"
18226
- 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"
18759
+ class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-500 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"
18227
18760
  aria-label="Expand all"
18228
18761
  [disabled]="rowList().length === 0"
18229
18762
  (click)="expandAllRows()"
@@ -18237,7 +18770,8 @@ class TableComponent {
18237
18770
  <th
18238
18771
  scope="col"
18239
18772
  [class]="headerCellClass(col)"
18240
- [style.width]="col.width ?? null"
18773
+ [style.width]="col.width ?? col.maxWidth ?? null"
18774
+ [style.max-width]="col.maxWidth ?? null"
18241
18775
  >
18242
18776
  @if (headerTemplateFor(col.key); as headerTpl) {
18243
18777
  <ng-container
@@ -18246,7 +18780,7 @@ class TableComponent {
18246
18780
  } @else if (col.sortable) {
18247
18781
  <button
18248
18782
  type="button"
18249
- 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"
18783
+ class="inline-flex cursor-pointer items-center gap-1 rounded-sm font-medium text-neutral-500 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"
18250
18784
  (click)="onSortHeaderClick(col.key)"
18251
18785
  >
18252
18786
  <span>{{ col.header }}</span>
@@ -18302,7 +18836,7 @@ class TableComponent {
18302
18836
  <td [class]="expandBodyClass(i)">
18303
18837
  <button
18304
18838
  type="button"
18305
- 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"
18839
+ 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"
18306
18840
  [attr.aria-label]="
18307
18841
  isRowExpanded(row, i)
18308
18842
  ? 'Collapse row details'
@@ -18321,7 +18855,8 @@ class TableComponent {
18321
18855
  @for (col of columns(); track col.key) {
18322
18856
  <td
18323
18857
  [class]="bodyCellClass(col, i)"
18324
- [style.width]="col.width ?? null"
18858
+ [style.width]="col.width ?? col.maxWidth ?? null"
18859
+ [style.max-width]="col.maxWidth ?? null"
18325
18860
  >
18326
18861
  @if (templateFor(col.key); as tpl) {
18327
18862
  <ng-container
@@ -18341,41 +18876,52 @@ class TableComponent {
18341
18876
  [class.border-border]="isRowExpanded(row, i)"
18342
18877
  [class.dark:border-white/10]="isRowExpanded(row, i)"
18343
18878
  >
18344
- <td class="p-0 align-top" [attr.colspan]="columns().length + 1">
18879
+ <td class="p-0 align-top" [attr.colspan]="colSpan()">
18880
+ <!-- Sticky must sit outside overflow-hidden or it pins
18881
+ to the full table width and scrolls with the row. -->
18345
18882
  <div
18346
- class="grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none"
18347
- [style.grid-template-rows]="
18348
- isRowExpanded(row, i) ? '1fr' : '0fr'
18349
- "
18350
- [attr.aria-hidden]="!isRowExpanded(row, i)"
18351
- [class.pointer-events-none]="!isRowExpanded(row, i)"
18883
+ class="africanies-table-expand-panel bg-surface-sunken dark:bg-white/[0.05]"
18884
+ [style.width.px]="scrollportWidthPx() || null"
18885
+ [style.maxWidth.px]="scrollportWidthPx() || null"
18352
18886
  >
18353
18887
  <div
18354
- class="min-h-0 overflow-hidden border-t border-border/70 bg-neutral-50 dark:border-white/10 dark:bg-white/[0.05]"
18888
+ class="grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none"
18889
+ [style.grid-template-rows]="
18890
+ isRowExpanded(row, i) ? '1fr' : '0fr'
18891
+ "
18892
+ [attr.aria-hidden]="!isRowExpanded(row, i)"
18893
+ [class.pointer-events-none]="!isRowExpanded(row, i)"
18355
18894
  >
18356
- <dl
18357
- 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"
18358
- [class.opacity-0]="!isRowExpanded(row, i)"
18359
- [class.opacity-100]="isRowExpanded(row, i)"
18895
+ <div
18896
+ class="min-h-0 overflow-x-hidden overflow-y-hidden border-t border-border-strong dark:border-white/10"
18360
18897
  >
18361
- @for (detail of rowDetailDefs(); track detail.label()) {
18362
- <div class="min-w-0">
18363
- <dt
18364
- class="m-0 text-caption font-medium text-neutral-500 dark:text-neutral-400"
18365
- >
18366
- {{ detail.label() }}
18367
- </dt>
18368
- <dd
18369
- class="m-0 mt-0.5 text-body-sm text-ink dark:text-white"
18370
- >
18371
- <ng-container
18372
- [ngTemplateOutlet]="detail.template"
18373
- [ngTemplateOutletContext]="cellContext(row)"
18374
- />
18375
- </dd>
18376
- </div>
18377
- }
18378
- </dl>
18898
+ <dl
18899
+ class="m-0 grid grid-cols-[repeat(auto-fill,minmax(11rem,1fr))] gap-x-6 gap-y-3 overflow-x-hidden px-[1.125rem] py-4 transition-opacity duration-200 ease-out motion-reduce:transition-none"
18900
+ [class.opacity-0]="!isRowExpanded(row, i)"
18901
+ [class.opacity-100]="isRowExpanded(row, i)"
18902
+ >
18903
+ @for (
18904
+ detail of rowDetailDefs();
18905
+ track detail.label()
18906
+ ) {
18907
+ <div class="min-w-0">
18908
+ <dt
18909
+ class="m-0 text-caption font-medium text-neutral-600 dark:text-neutral-400"
18910
+ >
18911
+ {{ detail.label() }}
18912
+ </dt>
18913
+ <dd
18914
+ class="m-0 mt-0.5 break-words text-body-sm text-ink dark:text-white"
18915
+ >
18916
+ <ng-container
18917
+ [ngTemplateOutlet]="detail.template"
18918
+ [ngTemplateOutletContext]="cellContext(row)"
18919
+ />
18920
+ </dd>
18921
+ </div>
18922
+ }
18923
+ </dl>
18924
+ </div>
18379
18925
  </div>
18380
18926
  </div>
18381
18927
  </td>
@@ -18386,6 +18932,23 @@ class TableComponent {
18386
18932
  }
18387
18933
  </tbody>
18388
18934
  </table>
18935
+ </div>
18936
+ @if (paginationLoading()) {
18937
+ <div
18938
+ class="absolute inset-0 z-[5] bg-surface/70 dark:bg-ink-950/70"
18939
+ data-testid="africanies-table-keep-rows-loading"
18940
+ aria-hidden="true"
18941
+ >
18942
+ <div
18943
+ class="sticky top-0 flex h-[min(70dvh,100%)] items-center justify-center px-4"
18944
+ >
18945
+ <africanies-loading-state
18946
+ mode="inline"
18947
+ [message]="loadingLabel()"
18948
+ />
18949
+ </div>
18950
+ </div>
18951
+ }
18389
18952
  </div>
18390
18953
 
18391
18954
  @if (meta(); as pager) {
@@ -18398,7 +18961,7 @@ class TableComponent {
18398
18961
  />
18399
18962
  }
18400
18963
  </div>
18401
- `, 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 });
18964
+ `, 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}:host .africanies-table-scrollport{container-type:inline-size;container-name:africanies-table}:host .africanies-table-expand-panel{position:sticky;left:0;z-index:1;box-sizing:border-box;width:100cqi;max-width:100cqi;overflow-x:hidden}\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"] }, { kind: "component", type: SegmentComponent, selector: "africanies-segment", inputs: ["items", "activeId", "ariaLabel", "linkActiveOptions", "keepScroll"], outputs: ["activeIdChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18402
18965
  }
18403
18966
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TableComponent, decorators: [{
18404
18967
  type: Component,
@@ -18411,6 +18974,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18411
18974
  ErrorStateComponent,
18412
18975
  LoadingStateComponent,
18413
18976
  PaginationComponent,
18977
+ SegmentComponent,
18414
18978
  ], host: {
18415
18979
  class: 'block w-full min-w-0',
18416
18980
  }, template: `
@@ -18418,84 +18982,107 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18418
18982
  class="flex w-full min-w-0 flex-col gap-3"
18419
18983
  [attr.aria-busy]="loading() || refreshing() || null"
18420
18984
  >
18421
- @if (showRefresh() || showFilter() || showExport()) {
18422
- <div class="flex items-center justify-between gap-2">
18423
- <div class="flex items-center gap-2">
18424
- @if (showToolbarRefresh()) {
18425
- <button
18426
- africanies-button
18427
- type="button"
18428
- variant="flat"
18429
- size="sm"
18430
- [disabled]="loading() || refreshing()"
18431
- [attr.aria-label]="refreshLabel()"
18432
- (click)="refreshClick.emit()"
18433
- >
18434
- <africanies-icon
18435
- name="refresh"
18436
- [size]="16"
18437
- [class]="refreshing() ? 'animate-spin' : ''"
18438
- />
18439
- {{ refreshLabel() }}
18440
- </button>
18441
- }
18442
- </div>
18443
- <div class="flex items-center gap-2">
18444
- @if (showFilter()) {
18445
- <button
18446
- africanies-button
18447
- type="button"
18448
- variant="flat"
18449
- size="sm"
18450
- [attr.aria-label]="filterLabel()"
18451
- (click)="filterClick.emit()"
18452
- >
18453
- <africanies-icon name="filter" [size]="16" />
18454
- {{ filterLabel() }}
18455
- @if (filterCount() > 0) {
18456
- <span
18457
- class="inline-flex min-w-5 items-center justify-center rounded-full bg-ink px-1.5 py-0.5 text-caption font-semibold tabular-nums text-white dark:bg-white dark:text-ink"
18985
+ @if (
18986
+ showShippingMode() ||
18987
+ showRefresh() ||
18988
+ showFilter() ||
18989
+ showExport()
18990
+ ) {
18991
+ <div class="flex w-full min-w-0 flex-col gap-3">
18992
+ @if (showShippingMode()) {
18993
+ <africanies-segment
18994
+ class="min-w-0 self-start"
18995
+ [items]="shippingModeItems"
18996
+ [activeId]="shippingModeActiveId()"
18997
+ ariaLabel="Shipping mode"
18998
+ (activeIdChange)="onShippingModeChange($event)"
18999
+ />
19000
+ }
19001
+ @if (showToolbarRefresh() || showFilter() || showExport()) {
19002
+ <div class="flex flex-wrap items-center justify-between gap-2">
19003
+ <div class="flex min-w-0 flex-wrap items-center gap-2">
19004
+ @if (showToolbarRefresh()) {
19005
+ <button
19006
+ africanies-button
19007
+ type="button"
19008
+ variant="flat"
19009
+ size="sm"
19010
+ [disabled]="loading() || refreshing()"
19011
+ [attr.aria-label]="refreshLabel()"
19012
+ (click)="refreshClick.emit()"
18458
19013
  >
18459
- {{ filterCount() }}
18460
- </span>
19014
+ <africanies-icon
19015
+ name="refresh"
19016
+ [size]="16"
19017
+ [class]="refreshing() ? 'animate-spin' : ''"
19018
+ />
19019
+ {{ refreshLabel() }}
19020
+ </button>
18461
19021
  }
18462
- </button>
18463
- }
18464
- @if (showExport()) {
18465
- <button
18466
- africanies-button
18467
- type="button"
18468
- variant="flat"
18469
- size="sm"
18470
- [attr.aria-label]="exportLabel()"
18471
- (click)="exportClick.emit()"
18472
- >
18473
- <africanies-icon name="download" [size]="16" />
18474
- {{ exportLabel() }}
18475
- </button>
18476
- }
18477
- </div>
19022
+ </div>
19023
+ <div class="flex items-center gap-2">
19024
+ @if (showFilter()) {
19025
+ <button
19026
+ africanies-button
19027
+ type="button"
19028
+ variant="flat"
19029
+ size="sm"
19030
+ [attr.aria-label]="filterLabel()"
19031
+ (click)="filterClick.emit()"
19032
+ >
19033
+ <africanies-icon name="filter" [size]="16" />
19034
+ {{ filterLabel() }}
19035
+ @if (filterCount() > 0) {
19036
+ <span
19037
+ class="inline-flex min-w-5 items-center justify-center rounded-full bg-ink px-1.5 py-0.5 text-caption font-semibold tabular-nums text-white dark:bg-white dark:text-ink"
19038
+ >
19039
+ {{ filterCount() }}
19040
+ </span>
19041
+ }
19042
+ </button>
19043
+ }
19044
+ @if (showExport()) {
19045
+ <button
19046
+ africanies-button
19047
+ type="button"
19048
+ variant="flat"
19049
+ size="sm"
19050
+ [attr.aria-label]="exportLabel()"
19051
+ (click)="exportClick.emit()"
19052
+ >
19053
+ <africanies-icon name="download" [size]="16" />
19054
+ {{ exportLabel() }}
19055
+ </button>
19056
+ }
19057
+ </div>
19058
+ </div>
19059
+ }
18478
19060
  </div>
18479
19061
  }
18480
19062
 
18481
19063
  @if (staleError(); as staleMessage) {
18482
- <div class="flex justify-end">
18483
- <africanies-error-indicator
18484
- class="max-w-[min(100%,20rem)]"
18485
- [error]="staleMessage"
18486
- retryText="Refresh"
18487
- [refreshing]="refreshing()"
18488
- (retry)="refreshClick.emit()"
18489
- />
18490
- </div>
19064
+ <africanies-error-indicator
19065
+ class="w-full"
19066
+ [error]="staleMessage"
19067
+ retryText="Refresh"
19068
+ [refreshing]="refreshing()"
19069
+ (retry)="refreshClick.emit()"
19070
+ />
18491
19071
  }
18492
19072
 
18493
19073
  <div
18494
- class="min-w-0 w-full overflow-x-auto rounded-md border border-border bg-white dark:border-white/10 dark:bg-ink-surface"
19074
+ class="relative min-w-0 w-full overflow-hidden rounded-panel border border-border bg-surface dark:border-white/10 dark:bg-ink-surface"
18495
19075
  >
18496
- <table
18497
- class="w-max min-w-full table-auto border-separate border-spacing-0 bg-inherit text-left text-body text-ink dark:text-white"
19076
+ <div
19077
+ #scrollport
19078
+ class="africanies-table-scrollport min-w-0 w-full overflow-x-auto"
19079
+ [class.lg:max-h-[min(70dvh,calc(100dvh-12rem))]]="stickyHeader()"
19080
+ [class.lg:overflow-auto]="stickyHeader() && !paginationLoading()"
19081
+ [class.pointer-events-none]="paginationLoading()"
18498
19082
  >
19083
+ <table
19084
+ class="w-max min-w-full table-auto border-separate border-spacing-0 bg-inherit text-left text-body text-ink dark:text-white"
19085
+ >
18499
19086
  <thead [class]="headerRowClass()">
18500
19087
  <tr>
18501
19088
  @if (isExpandable()) {
@@ -18506,7 +19093,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18506
19093
  @if (someRowsExpanded()) {
18507
19094
  <button
18508
19095
  type="button"
18509
- 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"
19096
+ class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-500 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"
18510
19097
  aria-label="Collapse all"
18511
19098
  (click)="collapseAllRows()"
18512
19099
  >
@@ -18515,7 +19102,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18515
19102
  } @else {
18516
19103
  <button
18517
19104
  type="button"
18518
- 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"
19105
+ class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-500 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"
18519
19106
  aria-label="Expand all"
18520
19107
  [disabled]="rowList().length === 0"
18521
19108
  (click)="expandAllRows()"
@@ -18529,7 +19116,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18529
19116
  <th
18530
19117
  scope="col"
18531
19118
  [class]="headerCellClass(col)"
18532
- [style.width]="col.width ?? null"
19119
+ [style.width]="col.width ?? col.maxWidth ?? null"
19120
+ [style.max-width]="col.maxWidth ?? null"
18533
19121
  >
18534
19122
  @if (headerTemplateFor(col.key); as headerTpl) {
18535
19123
  <ng-container
@@ -18538,7 +19126,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18538
19126
  } @else if (col.sortable) {
18539
19127
  <button
18540
19128
  type="button"
18541
- 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"
19129
+ class="inline-flex cursor-pointer items-center gap-1 rounded-sm font-medium text-neutral-500 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"
18542
19130
  (click)="onSortHeaderClick(col.key)"
18543
19131
  >
18544
19132
  <span>{{ col.header }}</span>
@@ -18594,7 +19182,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18594
19182
  <td [class]="expandBodyClass(i)">
18595
19183
  <button
18596
19184
  type="button"
18597
- 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"
19185
+ 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"
18598
19186
  [attr.aria-label]="
18599
19187
  isRowExpanded(row, i)
18600
19188
  ? 'Collapse row details'
@@ -18613,7 +19201,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18613
19201
  @for (col of columns(); track col.key) {
18614
19202
  <td
18615
19203
  [class]="bodyCellClass(col, i)"
18616
- [style.width]="col.width ?? null"
19204
+ [style.width]="col.width ?? col.maxWidth ?? null"
19205
+ [style.max-width]="col.maxWidth ?? null"
18617
19206
  >
18618
19207
  @if (templateFor(col.key); as tpl) {
18619
19208
  <ng-container
@@ -18633,41 +19222,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18633
19222
  [class.border-border]="isRowExpanded(row, i)"
18634
19223
  [class.dark:border-white/10]="isRowExpanded(row, i)"
18635
19224
  >
18636
- <td class="p-0 align-top" [attr.colspan]="columns().length + 1">
19225
+ <td class="p-0 align-top" [attr.colspan]="colSpan()">
19226
+ <!-- Sticky must sit outside overflow-hidden or it pins
19227
+ to the full table width and scrolls with the row. -->
18637
19228
  <div
18638
- class="grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none"
18639
- [style.grid-template-rows]="
18640
- isRowExpanded(row, i) ? '1fr' : '0fr'
18641
- "
18642
- [attr.aria-hidden]="!isRowExpanded(row, i)"
18643
- [class.pointer-events-none]="!isRowExpanded(row, i)"
19229
+ class="africanies-table-expand-panel bg-surface-sunken dark:bg-white/[0.05]"
19230
+ [style.width.px]="scrollportWidthPx() || null"
19231
+ [style.maxWidth.px]="scrollportWidthPx() || null"
18644
19232
  >
18645
19233
  <div
18646
- class="min-h-0 overflow-hidden border-t border-border/70 bg-neutral-50 dark:border-white/10 dark:bg-white/[0.05]"
19234
+ class="grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none"
19235
+ [style.grid-template-rows]="
19236
+ isRowExpanded(row, i) ? '1fr' : '0fr'
19237
+ "
19238
+ [attr.aria-hidden]="!isRowExpanded(row, i)"
19239
+ [class.pointer-events-none]="!isRowExpanded(row, i)"
18647
19240
  >
18648
- <dl
18649
- 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"
18650
- [class.opacity-0]="!isRowExpanded(row, i)"
18651
- [class.opacity-100]="isRowExpanded(row, i)"
19241
+ <div
19242
+ class="min-h-0 overflow-x-hidden overflow-y-hidden border-t border-border-strong dark:border-white/10"
18652
19243
  >
18653
- @for (detail of rowDetailDefs(); track detail.label()) {
18654
- <div class="min-w-0">
18655
- <dt
18656
- class="m-0 text-caption font-medium text-neutral-500 dark:text-neutral-400"
18657
- >
18658
- {{ detail.label() }}
18659
- </dt>
18660
- <dd
18661
- class="m-0 mt-0.5 text-body-sm text-ink dark:text-white"
18662
- >
18663
- <ng-container
18664
- [ngTemplateOutlet]="detail.template"
18665
- [ngTemplateOutletContext]="cellContext(row)"
18666
- />
18667
- </dd>
18668
- </div>
18669
- }
18670
- </dl>
19244
+ <dl
19245
+ class="m-0 grid grid-cols-[repeat(auto-fill,minmax(11rem,1fr))] gap-x-6 gap-y-3 overflow-x-hidden px-[1.125rem] py-4 transition-opacity duration-200 ease-out motion-reduce:transition-none"
19246
+ [class.opacity-0]="!isRowExpanded(row, i)"
19247
+ [class.opacity-100]="isRowExpanded(row, i)"
19248
+ >
19249
+ @for (
19250
+ detail of rowDetailDefs();
19251
+ track detail.label()
19252
+ ) {
19253
+ <div class="min-w-0">
19254
+ <dt
19255
+ class="m-0 text-caption font-medium text-neutral-600 dark:text-neutral-400"
19256
+ >
19257
+ {{ detail.label() }}
19258
+ </dt>
19259
+ <dd
19260
+ class="m-0 mt-0.5 break-words text-body-sm text-ink dark:text-white"
19261
+ >
19262
+ <ng-container
19263
+ [ngTemplateOutlet]="detail.template"
19264
+ [ngTemplateOutletContext]="cellContext(row)"
19265
+ />
19266
+ </dd>
19267
+ </div>
19268
+ }
19269
+ </dl>
19270
+ </div>
18671
19271
  </div>
18672
19272
  </div>
18673
19273
  </td>
@@ -18678,6 +19278,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18678
19278
  }
18679
19279
  </tbody>
18680
19280
  </table>
19281
+ </div>
19282
+ @if (paginationLoading()) {
19283
+ <div
19284
+ class="absolute inset-0 z-[5] bg-surface/70 dark:bg-ink-950/70"
19285
+ data-testid="africanies-table-keep-rows-loading"
19286
+ aria-hidden="true"
19287
+ >
19288
+ <div
19289
+ class="sticky top-0 flex h-[min(70dvh,100%)] items-center justify-center px-4"
19290
+ >
19291
+ <africanies-loading-state
19292
+ mode="inline"
19293
+ [message]="loadingLabel()"
19294
+ />
19295
+ </div>
19296
+ </div>
19297
+ }
18681
19298
  </div>
18682
19299
 
18683
19300
  @if (meta(); as pager) {
@@ -18690,8 +19307,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18690
19307
  />
18691
19308
  }
18692
19309
  </div>
18693
- `, styles: [":host tr.africanies-table-row:hover>td{background-color:#eef2f6}:host-context(.dark) tr.africanies-table-row:hover>td{background-color:#272729}\n"] }]
18694
- }], 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 }] }] } });
19310
+ `, styles: [":host tr.africanies-table-row:hover>td{background-color:#e9eef5}:host-context(.dark) tr.africanies-table-row:hover>td{background-color:#272729}:host .africanies-table-scrollport{container-type:inline-size;container-name:africanies-table}:host .africanies-table-expand-panel{position:sticky;left:0;z-index:1;box-sizing:border-box;width:100cqi;max-width:100cqi;overflow-x:hidden}\n"] }]
19311
+ }], ctorParameters: () => [], propDecorators: { scrollport: [{ type: i0.ViewChild, args: ['scrollport', { isSignal: true }] }], 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 }] }], showShippingMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "showShippingMode", 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 }] }] } });
18695
19312
 
18696
19313
  /**
18697
19314
  * Presentational table with template-based cells.
@@ -18837,7 +19454,7 @@ class StepperComponent {
18837
19454
  if (past) {
18838
19455
  return 'cursor-pointer text-ink hover:bg-background-welcome dark:text-white dark:hover:bg-white/10';
18839
19456
  }
18840
- return 'opacity-40 text-neutral-500 dark:text-neutral-400';
19457
+ return 'opacity-40 text-neutral-600 dark:text-neutral-400';
18841
19458
  }
18842
19459
  stepBadgeClass(index) {
18843
19460
  const base = 'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full border text-body-sm font-medium';
@@ -18845,7 +19462,7 @@ class StepperComponent {
18845
19462
  const c = this.modeColor.classes();
18846
19463
  return `${base} ${c.border} ${c.bg} text-white`;
18847
19464
  }
18848
- return `${base} border-border bg-white text-neutral-500 dark:border-white/20 dark:bg-ink-950 dark:text-neutral-400`;
19465
+ return `${base} border-control bg-surface text-neutral-600 dark:border-white/20 dark:bg-ink-950 dark:text-neutral-400`;
18849
19466
  }
18850
19467
  canActivate(index) {
18851
19468
  if (index === this.activeIndex()) {
@@ -18870,7 +19487,7 @@ class StepperComponent {
18870
19487
  <div class="flex flex-col gap-5 text-ink dark:text-white">
18871
19488
  <div class="flex flex-col gap-2">
18872
19489
  <p
18873
- class="m-0 text-caption font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-400"
19490
+ class="m-0 text-caption font-medium uppercase tracking-wide text-neutral-600 dark:text-neutral-400"
18874
19491
  >
18875
19492
  Step {{ activeIndex() + 1 }} of {{ steps().length }}
18876
19493
  <span
@@ -18899,7 +19516,7 @@ class StepperComponent {
18899
19516
  <li class="flex min-w-0 shrink-0 items-center gap-1">
18900
19517
  <button
18901
19518
  type="button"
18902
- 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"
19519
+ 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"
18903
19520
  [class]="headerButtonClass(i)"
18904
19521
  [attr.aria-current]="i === activeIndex() ? 'step' : null"
18905
19522
  [disabled]="!canActivate(i)"
@@ -18938,7 +19555,7 @@ class StepperComponent {
18938
19555
  }
18939
19556
  </div>
18940
19557
  </div>
18941
- `, 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 });
19558
+ `, 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 });
18942
19559
  }
18943
19560
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StepperComponent, decorators: [{
18944
19561
  type: Component,
@@ -18946,7 +19563,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18946
19563
  <div class="flex flex-col gap-5 text-ink dark:text-white">
18947
19564
  <div class="flex flex-col gap-2">
18948
19565
  <p
18949
- class="m-0 text-caption font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-400"
19566
+ class="m-0 text-caption font-medium uppercase tracking-wide text-neutral-600 dark:text-neutral-400"
18950
19567
  >
18951
19568
  Step {{ activeIndex() + 1 }} of {{ steps().length }}
18952
19569
  <span
@@ -18975,7 +19592,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
18975
19592
  <li class="flex min-w-0 shrink-0 items-center gap-1">
18976
19593
  <button
18977
19594
  type="button"
18978
- 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"
19595
+ 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"
18979
19596
  [class]="headerButtonClass(i)"
18980
19597
  [attr.aria-current]="i === activeIndex() ? 'step' : null"
18981
19598
  [disabled]="!canActivate(i)"
@@ -19014,7 +19631,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
19014
19631
  }
19015
19632
  </div>
19016
19633
  </div>
19017
- `, 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"] }]
19634
+ `, 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"] }]
19018
19635
  }], 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 }] }] } });
19019
19636
 
19020
19637
  /**
@@ -19275,6 +19892,7 @@ class AfricaniesNavigationModule {
19275
19892
  BreadcrumbComponent,
19276
19893
  TabsComponent,
19277
19894
  SegmentComponent,
19895
+ ShippingModeSwitchComponent,
19278
19896
  SideNavComponent] });
19279
19897
  }
19280
19898
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AfricaniesNavigationModule, decorators: [{