@africanies/angular-web-sdk 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/.angular/cache/22.0.9/playground/.tsbuildinfo +1 -1
  2. package/.nx/cache/{4082520855419352840 → 15302120112746851896}/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +30 -8
  3. package/.nx/cache/15302120112746851896/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -0
  4. package/.nx/cache/{4082520855419352840 → 15302120112746851896}/dist/libs/africanies-theme/tailwind-preset.cjs +89 -12
  5. package/.nx/cache/{4082520855419352840 → 15302120112746851896}/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +14 -4
  6. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +4639 -0
  7. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -0
  8. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +2748 -0
  9. package/.nx/cache/18393832731522001121/dist/libs/africanies-core/README.md +85 -0
  10. package/.nx/cache/{17491371155400585077 → 18393832731522001121}/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +139 -51
  11. package/.nx/cache/18393832731522001121/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -0
  12. package/.nx/cache/18393832731522001121/dist/libs/africanies-core/package.json +31 -0
  13. package/.nx/cache/{17491371155400585077 → 18393832731522001121}/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +14 -11
  14. package/.nx/cache/{3314955264390318442 → 3953970051755345887}/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +775 -566
  15. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  16. package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +150 -119
  17. package/.nx/cache/{12276756491762927325 → 4225324930345532464}/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +34 -6
  18. package/.nx/cache/4225324930345532464/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -0
  19. package/.nx/cache/{12276756491762927325 → 4225324930345532464}/dist/libs/africanies-theme/tailwind-preset.cjs +104 -19
  20. package/.nx/cache/{12276756491762927325 → 4225324930345532464}/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +21 -2
  21. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/README.md +69 -0
  22. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/assets/icons.sprite.svg +1851 -0
  23. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs +833 -0
  24. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs.map +1 -0
  25. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/package.json +30 -0
  26. package/.nx/cache/5151121439688073678/dist/libs/africanies-icons/types/africanies-africanies-icons.d.ts +108 -0
  27. package/.nx/cache/877026836685850193/dist/libs/africanies-models/README.md +60 -0
  28. package/.nx/cache/877026836685850193/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs +613 -0
  29. package/.nx/cache/877026836685850193/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs.map +1 -0
  30. package/.nx/cache/877026836685850193/dist/libs/africanies-models/package.json +23 -0
  31. package/.nx/cache/877026836685850193/dist/libs/africanies-models/types/africanies-africanies-models.d.ts +1452 -0
  32. package/.nx/cache/{17499961385238663835 → 9406544675747598148}/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +765 -615
  33. package/.nx/cache/9406544675747598148/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  34. package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +170 -130
  35. package/.nx/cache/run.json +36 -36
  36. package/.nx/cache/terminalOutputs/{3017748574492080228 → 12565224028063674529} +16 -7
  37. package/.nx/cache/terminalOutputs/{8648260256613557249 → 13478445034325959094} +175 -172
  38. package/.nx/cache/terminalOutputs/{7126777490381568487 → 15149466893522345489} +10 -5
  39. package/.nx/cache/terminalOutputs/{4082520855419352840 → 15302120112746851896} +1 -1
  40. package/.nx/cache/terminalOutputs/15835338512221847842 +185 -0
  41. package/.nx/cache/terminalOutputs/{17491371155400585077 → 17289624717336768573} +1 -1
  42. package/.nx/cache/terminalOutputs/17571479482482478257 +35 -0
  43. package/.nx/cache/terminalOutputs/{9408529484792607601 → 17960163310445607219} +1 -1
  44. package/.nx/cache/terminalOutputs/18393832731522001121 +21 -0
  45. package/.nx/cache/terminalOutputs/3088264223473143358 +8 -0
  46. package/.nx/cache/terminalOutputs/{3314955264390318442 → 3953970051755345887} +1 -1
  47. package/.nx/cache/terminalOutputs/{12276756491762927325 → 4225324930345532464} +1 -1
  48. package/.nx/cache/terminalOutputs/4363587767476846758 +176 -0
  49. package/.nx/cache/terminalOutputs/5151121439688073678 +21 -0
  50. package/.nx/cache/terminalOutputs/7462873189468732296 +17 -0
  51. package/.nx/cache/terminalOutputs/8032148447904822353 +184 -182
  52. package/.nx/cache/terminalOutputs/8193462679279018087 +39 -0
  53. package/.nx/cache/terminalOutputs/877026836685850193 +21 -0
  54. package/.nx/cache/terminalOutputs/9406544675747598148 +21 -0
  55. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db +0 -0
  56. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-shm +0 -0
  57. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-wal +0 -0
  58. package/.nx/workspace-data/d/daemon.log +6813 -0
  59. package/.nx/workspace-data/d/server-process.json +2 -2
  60. package/.nx/workspace-data/file-map.json +3490 -3432
  61. package/.nx/workspace-data/nx_files.nxt +0 -0
  62. package/.nx/workspace-data/project-graph.json +7 -2
  63. package/apps/playground/src/app/pages/tokens-page.ts +17 -13
  64. package/apps/playground/src/app/snippets/filters.snippets.ts +10 -0
  65. package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +6 -1
  66. package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -1
  67. package/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +6 -1
  68. package/dist/libs/africanies-icons/assets/icons.sprite.svg +7 -0
  69. package/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs +1 -0
  70. package/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs.map +1 -1
  71. package/dist/libs/africanies-icons/types/africanies-africanies-icons.d.ts +2 -2
  72. package/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs +22 -8
  73. package/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs.map +1 -1
  74. package/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +22 -10
  75. package/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -1
  76. package/dist/libs/africanies-theme/tailwind-preset.cjs +84 -16
  77. package/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +4 -2
  78. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +723 -618
  79. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -1
  80. package/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +142 -114
  81. package/libs/africanies-core/src/index.ts +1 -1
  82. package/libs/africanies-core/src/lib/notification/notification-link.ts +6 -1
  83. package/libs/africanies-icons/src/assets/icons.sprite.svg +7 -0
  84. package/libs/africanies-icons/src/lib/icon-name.ts +1 -0
  85. package/libs/africanies-models/src/lib/filters/configs/seed-configs.ts +22 -8
  86. package/libs/africanies-theme/src/lib/mode-color.safelist.ts +12 -0
  87. package/libs/africanies-theme/src/lib/mode-color.service.spec.ts +55 -0
  88. package/libs/africanies-theme/src/lib/mode-color.service.ts +11 -11
  89. package/libs/africanies-theme/tailwind-preset.cjs +84 -16
  90. package/libs/africanies-ui/src/lib/action-menu/action-menu.component.ts +3 -3
  91. package/libs/africanies-ui/src/lib/alert/alert.component.ts +4 -4
  92. package/libs/africanies-ui/src/lib/avatar/avatar-menu.component.ts +2 -2
  93. package/libs/africanies-ui/src/lib/button/button.component.spec.ts +7 -7
  94. package/libs/africanies-ui/src/lib/button/button.component.ts +5 -5
  95. package/libs/africanies-ui/src/lib/chip/chip.component.ts +4 -4
  96. package/libs/africanies-ui/src/lib/content-stack/content-stack.component.ts +1 -1
  97. package/libs/africanies-ui/src/lib/feedback/error-indicator.component.ts +3 -3
  98. package/libs/africanies-ui/src/lib/feedback/error-state.component.ts +2 -2
  99. package/libs/africanies-ui/src/lib/filters/filter-drawer.panel.ts +44 -27
  100. package/libs/africanies-ui/src/lib/forms/checkbox/checkbox.component.ts +2 -1
  101. package/libs/africanies-ui/src/lib/forms/file-upload/file-preview.dialog.ts +1 -1
  102. package/libs/africanies-ui/src/lib/forms/file-upload/file-upload.component.ts +1 -1
  103. package/libs/africanies-ui/src/lib/forms/form-field.classes.ts +36 -6
  104. package/libs/africanies-ui/src/lib/forms/otp-input/otp-input.component.ts +2 -1
  105. package/libs/africanies-ui/src/lib/forms/radio/radio.component.ts +2 -1
  106. package/libs/africanies-ui/src/lib/forms/select/select.component.ts +1 -1
  107. package/libs/africanies-ui/src/lib/forms/toggle/toggle.component.ts +1 -1
  108. package/libs/africanies-ui/src/lib/image/image.component.ts +1 -1
  109. package/libs/africanies-ui/src/lib/info-popover/info-popover.component.ts +2 -2
  110. package/libs/africanies-ui/src/lib/layout/app-shell-content-header.component.ts +1 -1
  111. package/libs/africanies-ui/src/lib/layout/app-shell-header.component.ts +14 -14
  112. package/libs/africanies-ui/src/lib/layout/app-shell.component.html +2 -2
  113. package/libs/africanies-ui/src/lib/layout/app-shell.component.ts +3 -3
  114. package/libs/africanies-ui/src/lib/layout/header-greeting.util.ts +0 -2
  115. package/libs/africanies-ui/src/lib/layout/header-weather.spec.ts +11 -40
  116. package/libs/africanies-ui/src/lib/layout/header-weather.ts +22 -63
  117. package/libs/africanies-ui/src/lib/navigation/shipping-mode-switch/shipping-mode-switch.component.ts +6 -6
  118. package/libs/africanies-ui/src/lib/navigation/side-nav/africanies-side-nav.component.html +21 -13
  119. package/libs/africanies-ui/src/lib/navigation/side-nav/africanies-side-nav.component.ts +13 -7
  120. package/libs/africanies-ui/src/lib/notifications/notification-drawer.panel.ts +6 -6
  121. package/libs/africanies-ui/src/lib/overlay/confirm-dialog.component.ts +2 -2
  122. package/libs/africanies-ui/src/lib/overlay/drawer.service.ts +2 -2
  123. package/libs/africanies-ui/src/lib/overlay/modal.service.ts +3 -3
  124. package/libs/africanies-ui/src/lib/overlay/overlay-frame.component.ts +1 -1
  125. package/libs/africanies-ui/src/lib/pagination/pagination.component.ts +1 -1
  126. package/libs/africanies-ui/src/lib/stepper/stepper.component.ts +7 -7
  127. package/libs/africanies-ui/src/lib/table/table-column.ts +9 -0
  128. package/libs/africanies-ui/src/lib/table/table.component.spec.ts +99 -0
  129. package/libs/africanies-ui/src/lib/table/table.component.ts +72 -30
  130. package/libs/africanies-ui/src/lib/toast/toast-item.component.ts +6 -6
  131. package/package.json +1 -1
  132. package/svg/hand-coins.svg +9 -0
  133. package/.nx/cache/12276756491762927325/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +0 -1
  134. package/.nx/cache/17491371155400585077/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +0 -1
  135. package/.nx/cache/17499961385238663835/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +0 -1
  136. package/.nx/cache/3314955264390318442/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +0 -1
  137. package/.nx/cache/4082520855419352840/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +0 -1
  138. package/.nx/cache/terminalOutputs/17499961385238663835 +0 -21
  139. /package/.nx/cache/{12276756491762927325 → 15302120112746851896}/dist/libs/africanies-theme/README.md +0 -0
  140. /package/.nx/cache/{12276756491762927325 → 15302120112746851896}/dist/libs/africanies-theme/package.json +0 -0
  141. /package/.nx/cache/{17491371155400585077 → 17289624717336768573}/dist/libs/africanies-core/README.md +0 -0
  142. /package/.nx/cache/{17491371155400585077 → 17289624717336768573}/dist/libs/africanies-core/package.json +0 -0
  143. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/README.md +0 -0
  144. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  145. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  146. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +0 -0
  147. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/assets/carriers/dhl.svg +0 -0
  148. /package/.nx/cache/{17499961385238663835 → 3953970051755345887}/dist/libs/africanies-ui/package.json +0 -0
  149. /package/.nx/cache/{4082520855419352840 → 4225324930345532464}/dist/libs/africanies-theme/README.md +0 -0
  150. /package/.nx/cache/{4082520855419352840 → 4225324930345532464}/dist/libs/africanies-theme/package.json +0 -0
  151. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/README.md +0 -0
  152. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  153. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  154. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +0 -0
  155. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/assets/carriers/dhl.svg +0 -0
  156. /package/.nx/cache/{3314955264390318442 → 9406544675747598148}/dist/libs/africanies-ui/package.json +0 -0
  157. /package/.nx/cache/terminalOutputs/{16279119293212733518 → 14005735098959303198} +0 -0
@@ -2,15 +2,12 @@ import type { IconName } from '@africanies/africanies-icons';
2
2
 
3
3
  import type { HeaderWeather, HeaderWeatherKind } from './header-greeting.util';
4
4
 
5
- /** Bump when cache shape or city resolution strategy changes. */
6
- const CACHE_KEY = 'africanies-header-weather-v4';
5
+ /** Bump when cache shape or lookup strategy changes. */
6
+ const CACHE_KEY = 'africanies-header-weather-v5';
7
7
  const FETCH_MS = 4_000;
8
8
  const BROWSER_GEO_MS = 3_500;
9
- /** IP fallback — city string is often wrong (e.g. NG IPs labelled Lagos). */
9
+ /** IP fallback for coordinates only — city strings are often wrong. */
10
10
  const GEO_URL = 'https://get.geojs.io/v1/ip/geo.json';
11
- /** Client reverse-geocode so the label matches the forecast coordinates. */
12
- const REVERSE_GEO_URL =
13
- 'https://api.bigdatacloud.net/data/reverse-geocode-client';
14
11
 
15
12
  const WEATHER_LABELS: Record<HeaderWeatherKind, string> = {
16
13
  clear: 'Clear',
@@ -26,21 +23,13 @@ interface CachedWeather {
26
23
  hour: string;
27
24
  kind: HeaderWeatherKind;
28
25
  temperatureC?: number;
29
- city?: string;
30
26
  }
31
27
 
32
28
  interface GeoJsPayload {
33
- city?: string;
34
29
  latitude?: string | number;
35
30
  longitude?: string | number;
36
31
  }
37
32
 
38
- interface ReverseGeoPayload {
39
- city?: string;
40
- locality?: string;
41
- principalSubdivision?: string;
42
- }
43
-
44
33
  interface OpenMeteoPayload {
45
34
  current?: {
46
35
  weather_code?: number;
@@ -88,9 +77,16 @@ export function mapWmoWeatherCode(code: number): HeaderWeatherKind | null {
88
77
  * Short condition label for the header weather chip.
89
78
  *
90
79
  * @param kind - Coarse forecast kind.
91
- * @returns Display label such as `Rain` or `Clear`.
80
+ * @param hour - Local hour; clear days show as Sunny, nights as Clear.
81
+ * @returns Display label such as `Rain` or `Sunny`.
92
82
  */
93
- export function headerWeatherLabel(kind: HeaderWeatherKind): string {
83
+ export function headerWeatherLabel(
84
+ kind: HeaderWeatherKind,
85
+ hour = new Date().getHours(),
86
+ ): string {
87
+ if (kind === 'clear') {
88
+ return hour >= 19 || hour < 6 ? 'Clear' : 'Sunny';
89
+ }
94
90
  return WEATHER_LABELS[kind];
95
91
  }
96
92
 
@@ -112,12 +108,12 @@ export function headerWeatherIcon(kind: HeaderWeatherKind, hour: number): IconNa
112
108
  }
113
109
 
114
110
  /**
115
- * City-level forecast via browser / IP geolocation + Open-Meteo (no API key).
111
+ * Local forecast via browser / IP coordinates + Open-Meteo (no API key).
116
112
  *
117
- * Coordinates prefer the browser Geolocation API (accurate for travellers;
118
- * may prompt once). Otherwise IP geo is used. The city label is reverse-
119
- * geocoded from those coordinates IP `city` strings are often wrong for
120
- * Nigeria (many networks resolve as Lagos).
113
+ * Coordinates prefer the browser Geolocation API (may prompt once).
114
+ * Otherwise IP geo is used for lat/lon only — city labels are not shown
115
+ * because IP and reverse-geocode cities are often wrong (e.g. NG IPs
116
+ * labelled Lagos).
121
117
  *
122
118
  * Fails closed: missing browser APIs, timeouts, and HTTP errors all return
123
119
  * `null` so the greeting can stay time-of-day only. Cached per local hour
@@ -162,11 +158,8 @@ export async function loadHeaderWeather(
162
158
  }
163
159
 
164
160
  const temperatureC = asNumber(forecast?.current?.temperature_2m) ?? undefined;
165
- const city =
166
- (await reverseGeocodeCity(run, coords.latitude, coords.longitude)) ??
167
- coords.fallbackCity;
168
- const weather: HeaderWeather = { kind, temperatureC, city };
169
- writeCache({ hour, kind, temperatureC, city });
161
+ const weather: HeaderWeather = { kind, temperatureC };
162
+ writeCache({ hour, kind, temperatureC });
170
163
  return weather;
171
164
  } catch {
172
165
  return null;
@@ -194,7 +187,6 @@ function readCache(hour: string): HeaderWeather | null {
194
187
  return {
195
188
  kind: parsed.kind,
196
189
  temperatureC: parsed.temperatureC,
197
- city: parsed.city,
198
190
  };
199
191
  } catch {
200
192
  return null;
@@ -211,12 +203,11 @@ function writeCache(value: CachedWeather): void {
211
203
 
212
204
  /**
213
205
  * Prefer device coordinates when available (may prompt once); otherwise fall
214
- * back to IP geo. IP city labels for Nigeria are often wrong — reverse
215
- * geocoding from these coordinates still depends on accurate lat/lon.
206
+ * back to IP lat/lon for the forecast only.
216
207
  */
217
208
  async function resolveCoordinates(
218
209
  fetchFn: typeof fetch,
219
- ): Promise<(Coordinates & { fallbackCity?: string }) | null> {
210
+ ): Promise<Coordinates | null> {
220
211
  const browser = await tryBrowserGeolocation();
221
212
  if (browser) {
222
213
  return browser;
@@ -228,11 +219,7 @@ async function resolveCoordinates(
228
219
  if (latitude === null || longitude === null) {
229
220
  return null;
230
221
  }
231
- return {
232
- latitude,
233
- longitude,
234
- fallbackCity: asCity(geo?.city),
235
- };
222
+ return { latitude, longitude };
236
223
  }
237
224
 
238
225
  async function tryBrowserGeolocation(): Promise<Coordinates | null> {
@@ -266,23 +253,6 @@ async function tryBrowserGeolocation(): Promise<Coordinates | null> {
266
253
  });
267
254
  }
268
255
 
269
- async function reverseGeocodeCity(
270
- fetchFn: typeof fetch,
271
- latitude: number,
272
- longitude: number,
273
- ): Promise<string | undefined> {
274
- const url =
275
- `${REVERSE_GEO_URL}?latitude=${encodeURIComponent(String(latitude))}` +
276
- `&longitude=${encodeURIComponent(String(longitude))}` +
277
- `&localityLanguage=en`;
278
- const payload = (await fetchJson(fetchFn, url)) as ReverseGeoPayload | null;
279
- return (
280
- asCity(payload?.city) ??
281
- asCity(payload?.locality) ??
282
- asCity(payload?.principalSubdivision)
283
- );
284
- }
285
-
286
256
  function asNumber(value: unknown): number | null {
287
257
  if (typeof value === 'number' && Number.isFinite(value)) {
288
258
  return value;
@@ -294,17 +264,6 @@ function asNumber(value: unknown): number | null {
294
264
  return null;
295
265
  }
296
266
 
297
- function asCity(value: unknown): string | undefined {
298
- if (typeof value !== 'string') {
299
- return undefined;
300
- }
301
- const trimmed = value.trim();
302
- if (!trimmed || trimmed.toLowerCase() === 'n/a') {
303
- return undefined;
304
- }
305
- return trimmed;
306
- }
307
-
308
267
  async function fetchJson(
309
268
  fetchFn: typeof fetch,
310
269
  url: string,
@@ -131,8 +131,8 @@ export class ShippingModeSwitchComponent {
131
131
  /** Host padding / divider — tighter when the rail is collapsed. */
132
132
  protected readonly hostClass = computed(() =>
133
133
  this.collapsed()
134
- ? 'block shrink-0 border-t border-strong px-1.5 py-3 dark:border-white/10'
135
- : 'block shrink-0 border-t border-strong px-2 py-3 dark:border-white/10',
134
+ ? 'block shrink-0 border-t border-border px-1.5 py-3 dark:border-white/10'
135
+ : 'block shrink-0 border-t border-border px-2 py-3 dark:border-white/10',
136
136
  );
137
137
 
138
138
  /**
@@ -159,18 +159,18 @@ export class ShippingModeSwitchComponent {
159
159
  const compact = this.collapsed();
160
160
  const base =
161
161
  'flex w-full cursor-pointer flex-col items-center justify-center gap-1 rounded-lg border text-center text-caption font-medium transition-colors ' +
162
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink ' +
162
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus ' +
163
163
  (compact ? 'min-h-9 px-1 py-1.5' : 'min-h-[4.5rem] px-1.5 py-2');
164
164
  const idle =
165
- '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';
165
+ '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';
166
166
 
167
167
  if (option === 'stn') {
168
168
  return selected
169
- ? `${base} border-import bg-import text-white`
169
+ ? `${base} border-import-strong bg-import-strong text-white dark:border-import dark:bg-import`
170
170
  : `${base} ${idle}`;
171
171
  }
172
172
  return selected
173
- ? `${base} border-export bg-export text-white`
173
+ ? `${base} border-export-strong bg-export-strong text-white dark:border-export dark:bg-export`
174
174
  : `${base} ${idle}`;
175
175
  }
176
176
  }
@@ -1,5 +1,5 @@
1
1
  <aside
2
- 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"
2
+ 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"
3
3
  [style.width]="collapsed() ? '3.75rem' : '15rem'"
4
4
  [attr.aria-label]="ariaLabel()"
5
5
  >
@@ -10,7 +10,7 @@
10
10
  ></div>
11
11
 
12
12
  <div
13
- class="flex shrink-0 items-center gap-2 border-b border-border py-3 dark:border-white/10"
13
+ 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"
14
14
  [class.flex-col]="collapsed()"
15
15
  [class.px-3]="collapsed()"
16
16
  [class.pl-4]="!collapsed()"
@@ -43,7 +43,8 @@
43
43
  }
44
44
  <button
45
45
  type="button"
46
- 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"
46
+ 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"
47
+ data-onboarding="nav-collapse"
47
48
  [attr.aria-label]="
48
49
  collapsed() ? 'Expand navigation' : 'Collapse navigation'
49
50
  "
@@ -57,21 +58,28 @@
57
58
  </button>
58
59
  </div>
59
60
 
60
- <nav class="africanies-overlay-scroll relative flex-1 overflow-y-auto overflow-x-visible px-3 py-3">
61
+ <nav
62
+ class="africanies-overlay-scroll relative min-h-0 flex-1 overflow-y-auto overflow-x-visible px-3 py-3"
63
+ >
61
64
  @if (!collapsed() && expandParents() && hasOpenBranches()) {
62
- <button
63
- type="button"
64
- 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"
65
- aria-label="Collapse all"
66
- (click)="collapseAllBranches()"
67
- >
68
- <africanies-icon name="compress" [size]="14" />
69
- </button>
65
+ <!-- Zero-height sticky host: keeps the control pinned without reserving a row. -->
66
+ <div class="sticky top-0 z-10 h-0 overflow-visible">
67
+ <button
68
+ type="button"
69
+ class="absolute right-0 top-0 inline-flex size-7 cursor-pointer items-center justify-center rounded-md bg-surface text-neutral-600 shadow-sm ring-1 ring-border-strong transition-colors hover:bg-background-hover hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:bg-ink-950 dark:text-neutral-400 dark:ring-white/15 dark:hover:bg-white/10 dark:hover:text-white"
70
+ data-onboarding="nav-branches-collapse"
71
+ aria-label="Collapse all"
72
+ (click)="collapseAllBranches()"
73
+ >
74
+ <africanies-icon name="compress" [size]="14" />
75
+ </button>
76
+ </div>
70
77
  }
71
78
  <ul class="m-0 flex list-none flex-col gap-0.5 p-0">
72
79
  @for (item of items(); track item.id) {
73
80
  <li
74
- class="relative overflow-visible"
81
+ class="relative overflow-visible last:mb-0"
82
+ [class.mb-3]="!collapsed() && !item.children?.length"
75
83
  (mouseenter)="onItemEnter(item.id)"
76
84
  (mouseleave)="onItemLeave(item.id)"
77
85
  >
@@ -46,7 +46,8 @@ const DEFAULT_LINK_ACTIVE: IsActiveMatchOptions = {
46
46
  * ## Parent items with children
47
47
  * **Expanded rail:** branches start open by default (`expandBranchesByDefault`).
48
48
  * Clicking a parent only opens/closes the branch (no route change). When any
49
- * branch is open, a collapse-all control sits above the list (top right).
49
+ * branch is open, a sticky collapse-all control sits above the list (top right).
50
+ * The rail expand/collapse control stays pinned in the logo header.
50
51
  * **Collapsed rail:** clicking a parent expands the rail, opens the
51
52
  * branch, and activates the first enabled child (router or `activeId`).
52
53
  *
@@ -73,7 +74,7 @@ const DEFAULT_LINK_ACTIVE: IsActiveMatchOptions = {
73
74
  changeDetection: ChangeDetectionStrategy.OnPush,
74
75
  imports: [AfricaniesIconComponent, RouterLink, NgTemplateOutlet],
75
76
  host: {
76
- class: 'relative z-20 block h-full',
77
+ class: 'relative z-20 block h-full min-h-0',
77
78
  },
78
79
  styleUrl: './africanies-side-nav.component.css',
79
80
  templateUrl: './africanies-side-nav.component.html',
@@ -197,10 +198,15 @@ export class SideNavComponent {
197
198
  }
198
199
 
199
200
  /** Fold every parent branch on the expanded rail. */
200
- protected collapseAllBranches(): void {
201
+ collapseAllBranches(): void {
201
202
  this.openBranches.set(new Set());
202
203
  }
203
204
 
205
+ /** Open every parent branch (e.g. so the collapse-all control is visible). */
206
+ expandAllBranches(): void {
207
+ this.openBranches.set(new Set(this.collectParentIds(this.items())));
208
+ }
209
+
204
210
  protected onItemEnter(id: string): void {
205
211
  if (this.collapsed()) {
206
212
  this.hoverId.set(id);
@@ -239,7 +245,7 @@ export class SideNavComponent {
239
245
  protected rowClass(item: AfricaniesSideNavItem): string {
240
246
  const base =
241
247
  'group relative flex w-full items-center gap-2 rounded-lg text-body-sm no-underline transition-colors ' +
242
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink ' +
248
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus ' +
243
249
  (this.collapsed() ? 'justify-center px-1.5 py-2' : 'px-3 py-2') +
244
250
  (item.disabled ? ' cursor-not-allowed opacity-50' : ' cursor-pointer');
245
251
 
@@ -256,7 +262,7 @@ export class SideNavComponent {
256
262
  return `${base} bg-transparent ${colors.text} font-semibold`;
257
263
  }
258
264
  return (
259
- `${base} font-medium text-neutral-600 hover:bg-background-welcome hover:text-ink ` +
265
+ `${base} font-medium text-neutral-600 hover:bg-background-hover hover:text-ink ` +
260
266
  'dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white'
261
267
  );
262
268
  }
@@ -264,14 +270,14 @@ export class SideNavComponent {
264
270
  protected childRowClass(item: AfricaniesSideNavItem): string {
265
271
  const base =
266
272
  'relative flex w-full items-center gap-2 rounded-lg px-3 py-1.5 text-body-sm no-underline transition-colors ' +
267
- 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink ' +
273
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus ' +
268
274
  (item.disabled ? 'cursor-not-allowed opacity-50 ' : 'cursor-pointer ');
269
275
 
270
276
  if (this.isLeafActive(item)) {
271
277
  return `${base} ${this.modeColor.classes().activeFill} font-semibold`;
272
278
  }
273
279
  return (
274
- `${base} font-medium text-neutral-600 hover:bg-background-welcome hover:text-ink ` +
280
+ `${base} font-medium text-neutral-600 hover:bg-background-hover hover:text-ink ` +
275
281
  'dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white'
276
282
  );
277
283
  }
@@ -66,7 +66,7 @@ const MARK_ALL_CONFIRM_MS = 4000;
66
66
  {{ title() }}
67
67
  </h2>
68
68
  @if (hasUnread()) {
69
- <p class="m-0 text-caption text-neutral-500 dark:text-neutral-400">
69
+ <p class="m-0 text-caption text-neutral-600 dark:text-neutral-400">
70
70
  {{ unreadCount() }} unread
71
71
  </p>
72
72
  }
@@ -112,7 +112,7 @@ const MARK_ALL_CONFIRM_MS = 4000;
112
112
  <africanies-icon
113
113
  name="spinner"
114
114
  [size]="24"
115
- class="animate-spin text-neutral-500 dark:text-neutral-400"
115
+ class="animate-spin text-neutral-600 dark:text-neutral-400"
116
116
  />
117
117
  <p class="m-0 text-body-sm text-neutral-600 dark:text-neutral-400">
118
118
  Loading notifications…
@@ -179,7 +179,7 @@ const MARK_ALL_CONFIRM_MS = 4000;
179
179
  </h3>
180
180
  @if (item.timestamp) {
181
181
  <time
182
- class="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-neutral-500 dark:text-neutral-400"
182
+ class="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-neutral-600 dark:text-neutral-400"
183
183
  [dateTime]="item.timestamp"
184
184
  >
185
185
  {{ formatTimestamp(item.timestamp) }}
@@ -258,7 +258,7 @@ const MARK_ALL_CONFIRM_MS = 4000;
258
258
  >
259
259
  @if (loadingMore()) {
260
260
  <span
261
- class="inline-flex items-center gap-2 text-caption text-neutral-500 dark:text-neutral-400"
261
+ class="inline-flex items-center gap-2 text-caption text-neutral-600 dark:text-neutral-400"
262
262
  aria-live="polite"
263
263
  >
264
264
  <africanies-icon name="spinner" [size]="14" class="animate-spin" />
@@ -290,9 +290,9 @@ export class NotificationDrawerPanel {
290
290
  const base =
291
291
  'inline-flex cursor-pointer items-center gap-1 self-start text-caption font-semibold underline underline-offset-[3px] transition-colors';
292
292
  if (this.shipping.mode() === 'stn') {
293
- return `${base} text-import decoration-import/35 hover:decoration-import dark:text-import-light dark:decoration-import-light/40 dark:hover:decoration-import-light`;
293
+ 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`;
294
294
  }
295
- return `${base} text-export decoration-export/35 hover:decoration-export dark:text-export-light dark:decoration-export-light/40 dark:hover:decoration-export-light`;
295
+ 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`;
296
296
  });
297
297
 
298
298
  protected readonly title = computed(() => this.data.title ?? 'Notifications');
@@ -161,8 +161,8 @@ export class ConfirmDialogComponent {
161
161
  const well =
162
162
  'inline-flex size-14 shrink-0 items-center justify-center rounded-2xl';
163
163
  return this.options.danger
164
- ? `${well} bg-danger-subtle text-danger dark:bg-danger/20 dark:text-danger`
165
- : `${well} bg-export-subtle text-export dark:bg-export/20 dark:text-export-light`;
164
+ ? `${well} bg-danger-subtle text-danger-dark dark:bg-danger/20 dark:text-danger`
165
+ : `${well} bg-export-subtle text-export-strong dark:bg-export/20 dark:text-export-light`;
166
166
  });
167
167
 
168
168
  constructor() {
@@ -63,11 +63,11 @@ export class DrawerService implements OverlayOpener {
63
63
  ],
64
64
  panelClass: [
65
65
  'africanies-drawer-panel',
66
- 'bg-white',
66
+ 'bg-surface',
67
67
  'dark:bg-ink-950',
68
68
  'text-ink',
69
69
  'dark:text-white',
70
- 'shadow-xl',
70
+ 'shadow-floating',
71
71
  'border-l',
72
72
  'border-border',
73
73
  'dark:border-white/15',
@@ -151,12 +151,12 @@ export class ModalService implements OverlayOpener {
151
151
  ],
152
152
  panelClass: [
153
153
  'africanies-modal-panel',
154
- 'bg-white',
154
+ 'bg-surface',
155
155
  'dark:bg-ink-950',
156
156
  'text-ink',
157
157
  'dark:text-white',
158
- 'rounded-lg',
159
- 'shadow-xl',
158
+ 'rounded-panel',
159
+ 'shadow-floating',
160
160
  'border',
161
161
  'border-border',
162
162
  'dark:border-white/15',
@@ -83,7 +83,7 @@ export class OverlayFooterDirective {}
83
83
  <ng-content select="[africaniesOverlayHeader]" />
84
84
  </div>
85
85
  <div
86
- class="africanies-overlay-scroll min-h-0 flex-1 overflow-x-clip overflow-y-auto px-[2px] -mx-[2px]"
86
+ class="africanies-overlay-scroll min-h-0 flex-1 overflow-x-clip overflow-y-auto px-1 -mx-1"
87
87
  >
88
88
  <ng-content />
89
89
  </div>
@@ -144,7 +144,7 @@ type PageItem = number | 'ellipsis';
144
144
  @for (item of pageItems(); track trackPageItem(item, $index)) {
145
145
  @if (item === 'ellipsis') {
146
146
  <span
147
- class="inline-flex min-w-8 items-center justify-center text-body-sm text-neutral-500 dark:text-neutral-400"
147
+ class="inline-flex min-w-8 items-center justify-center text-body-sm text-neutral-600 dark:text-neutral-400"
148
148
  aria-hidden="true"
149
149
  >
150
150
 
@@ -53,9 +53,9 @@ function prefersReducedMotion(): boolean {
53
53
  .africanies-step-body {
54
54
  min-width: 0;
55
55
  overflow-x: clip;
56
- /* 2 px breathing room so focus-outline rings on edge fields are not clipped */
57
- padding-inline: 2px;
58
- margin-inline: -2px;
56
+ /* 4 px breathing room so any remaining external focus rings are not clipped */
57
+ padding-inline: 4px;
58
+ margin-inline: -4px;
59
59
  }
60
60
  .africanies-step-pane {
61
61
  min-width: 0;
@@ -66,7 +66,7 @@ function prefersReducedMotion(): boolean {
66
66
  <div class="flex flex-col gap-5 text-ink dark:text-white">
67
67
  <div class="flex flex-col gap-2">
68
68
  <p
69
- class="m-0 text-caption font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-400"
69
+ class="m-0 text-caption font-medium uppercase tracking-wide text-neutral-600 dark:text-neutral-400"
70
70
  >
71
71
  Step {{ activeIndex() + 1 }} of {{ steps().length }}
72
72
  <span
@@ -95,7 +95,7 @@ function prefersReducedMotion(): boolean {
95
95
  <li class="flex min-w-0 shrink-0 items-center gap-1">
96
96
  <button
97
97
  type="button"
98
- 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"
98
+ 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"
99
99
  [class]="headerButtonClass(i)"
100
100
  [attr.aria-current]="i === activeIndex() ? 'step' : null"
101
101
  [disabled]="!canActivate(i)"
@@ -223,7 +223,7 @@ export class StepperComponent {
223
223
  if (past) {
224
224
  return 'cursor-pointer text-ink hover:bg-background-welcome dark:text-white dark:hover:bg-white/10';
225
225
  }
226
- return 'opacity-40 text-neutral-500 dark:text-neutral-400';
226
+ return 'opacity-40 text-neutral-600 dark:text-neutral-400';
227
227
  }
228
228
 
229
229
  protected stepBadgeClass(index: number): string {
@@ -233,7 +233,7 @@ export class StepperComponent {
233
233
  const c = this.modeColor.classes();
234
234
  return `${base} ${c.border} ${c.bg} text-white`;
235
235
  }
236
- return `${base} border-border bg-white text-neutral-500 dark:border-white/20 dark:bg-ink-950 dark:text-neutral-400`;
236
+ return `${base} border-control bg-surface text-neutral-600 dark:border-white/20 dark:bg-ink-950 dark:text-neutral-400`;
237
237
  }
238
238
 
239
239
  protected canActivate(index: number): boolean {
@@ -34,6 +34,15 @@ export interface TableColumn<T = unknown> {
34
34
  */
35
35
  width?: string;
36
36
 
37
+ /**
38
+ * Optional CSS max-width (e.g. `'14rem'`).
39
+ *
40
+ * Caps growth for text-heavy columns without forcing a large minimum the
41
+ * way {@link width} can. Prefer this for Name / Description / Message when
42
+ * other columns should stay compact.
43
+ */
44
+ maxWidth?: string;
45
+
37
46
  /**
38
47
  * Pin the column while the table scrolls horizontally.
39
48
  *
@@ -0,0 +1,99 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
3
+
4
+ import { ShippingModeService } from '@africanies/africanies-core';
5
+ import type { ShippingMode } from '@africanies/africanies-models';
6
+
7
+ import { TableComponent } from './table.component';
8
+ import type { TableColumn } from './table-column';
9
+
10
+ interface Row {
11
+ id: number;
12
+ name: string;
13
+ }
14
+
15
+ @Component({
16
+ standalone: true,
17
+ imports: [TableComponent],
18
+ template: `
19
+ <africanies-table
20
+ [columns]="columns"
21
+ [rows]="rows()"
22
+ [loading]="loading()"
23
+ [error]="error()"
24
+ loadingLabel="Loading page…"
25
+ />
26
+ `,
27
+ })
28
+ class TableHostComponent {
29
+ readonly columns: TableColumn<Row>[] = [{ key: 'name', header: 'Name' }];
30
+ readonly rows = signal<Row[]>([]);
31
+ readonly loading = signal(false);
32
+ readonly error = signal<string | null>(null);
33
+ }
34
+
35
+ describe('TableComponent keep-rows loading', () => {
36
+ let fixture: ComponentFixture<TableHostComponent>;
37
+ let host: TableHostComponent;
38
+
39
+ beforeEach(async () => {
40
+ const mode = signal<ShippingMode>('sfn');
41
+ await TestBed.configureTestingModule({
42
+ imports: [TableHostComponent],
43
+ providers: [
44
+ {
45
+ provide: ShippingModeService,
46
+ useValue: {
47
+ mode: mode.asReadonly(),
48
+ },
49
+ },
50
+ ],
51
+ }).compileComponents();
52
+
53
+ fixture = TestBed.createComponent(TableHostComponent);
54
+ host = fixture.componentInstance;
55
+ fixture.detectChanges();
56
+ });
57
+
58
+ afterEach(() => {
59
+ fixture.destroy();
60
+ });
61
+
62
+ it('shows a keep-rows overlay when loading with rows on screen', () => {
63
+ host.rows.set([{ id: 1, name: 'Ada' }]);
64
+ host.loading.set(true);
65
+ fixture.detectChanges();
66
+
67
+ const root = fixture.nativeElement as HTMLElement;
68
+ expect(
69
+ root.querySelector('[data-testid="africanies-table-keep-rows-loading"]'),
70
+ ).not.toBeNull();
71
+ expect(root.textContent).toContain('Ada');
72
+ expect(root.textContent).toContain('Loading page…');
73
+ });
74
+
75
+ it('uses the in-grid body loader on first load with no rows', () => {
76
+ host.rows.set([]);
77
+ host.loading.set(true);
78
+ fixture.detectChanges();
79
+
80
+ const root = fixture.nativeElement as HTMLElement;
81
+ expect(
82
+ root.querySelector('[data-testid="africanies-table-keep-rows-loading"]'),
83
+ ).toBeNull();
84
+ expect(root.textContent).toContain('Loading page…');
85
+ });
86
+
87
+ it('shows a full-width stale error when rows remain after a failed fetch', () => {
88
+ host.rows.set([{ id: 1, name: 'Ada' }]);
89
+ host.error.set('Could not load page.');
90
+ fixture.detectChanges();
91
+
92
+ const root = fixture.nativeElement as HTMLElement;
93
+ const indicator = root.querySelector('africanies-error-indicator');
94
+ expect(indicator).not.toBeNull();
95
+ expect(indicator?.className).toContain('w-full');
96
+ expect(root.textContent).toContain('Could not load page.');
97
+ expect(root.textContent).toContain('Ada');
98
+ });
99
+ });