@africanies/angular-web-sdk 0.1.9 → 0.1.11

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 (70) hide show
  1. package/.nx/cache/1149535958171016935/dist/libs/africanies-theme/README.md +46 -0
  2. package/.nx/cache/1149535958171016935/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +243 -0
  3. package/.nx/cache/1149535958171016935/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -0
  4. package/.nx/cache/1149535958171016935/dist/libs/africanies-theme/package.json +35 -0
  5. package/.nx/cache/1149535958171016935/dist/libs/africanies-theme/tailwind-preset.cjs +330 -0
  6. package/.nx/cache/1149535958171016935/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +178 -0
  7. package/.nx/cache/14892813664845064572/dist/libs/africanies-ui/README.md +68 -0
  8. package/.nx/cache/14892813664845064572/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  9. package/.nx/cache/14892813664845064572/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  10. package/.nx/cache/14892813664845064572/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +5 -0
  11. package/.nx/cache/14892813664845064572/dist/libs/africanies-ui/assets/carriers/dhl.svg +7 -0
  12. package/.nx/cache/14892813664845064572/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +20013 -0
  13. package/.nx/cache/14892813664845064572/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  14. package/.nx/cache/14892813664845064572/dist/libs/africanies-ui/package.json +35 -0
  15. package/.nx/cache/14892813664845064572/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +5617 -0
  16. package/.nx/cache/3735952477975556635/dist/libs/africanies-ui/README.md +68 -0
  17. package/.nx/cache/3735952477975556635/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  18. package/.nx/cache/3735952477975556635/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  19. package/.nx/cache/3735952477975556635/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +5 -0
  20. package/.nx/cache/3735952477975556635/dist/libs/africanies-ui/assets/carriers/dhl.svg +7 -0
  21. package/.nx/cache/3735952477975556635/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +19998 -0
  22. package/.nx/cache/3735952477975556635/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  23. package/.nx/cache/3735952477975556635/dist/libs/africanies-ui/package.json +35 -0
  24. package/.nx/cache/3735952477975556635/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +5616 -0
  25. package/.nx/cache/run.json +18 -18
  26. package/.nx/cache/terminalOutputs/1149535958171016935 +21 -0
  27. package/.nx/cache/terminalOutputs/14892813664845064572 +21 -0
  28. package/.nx/cache/terminalOutputs/17909810599991457481 +40 -0
  29. package/.nx/cache/terminalOutputs/2927637324428060203 +42 -0
  30. package/.nx/cache/terminalOutputs/3735952477975556635 +21 -0
  31. package/.nx/cache/terminalOutputs/4787481949164239892 +8 -0
  32. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db +0 -0
  33. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-shm +0 -0
  34. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-wal +0 -0
  35. package/.nx/workspace-data/d/daemon.log +3807 -0
  36. package/.nx/workspace-data/d/server-process.json +2 -2
  37. package/.nx/workspace-data/file-map.json +3254 -3260
  38. package/.nx/workspace-data/nx_files.nxt +0 -0
  39. package/.nx/workspace-data/project-graph.json +2 -2
  40. package/README.md +0 -17
  41. package/apps/playground/src/app/pages/chip-page.ts +11 -1
  42. package/apps/playground/src/app/pages/tokens-page.ts +2 -2
  43. package/apps/playground/src/app/snippets/chip.snippets.ts +5 -0
  44. package/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +8 -8
  45. package/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -1
  46. package/dist/libs/africanies-theme/tailwind-preset.cjs +3 -3
  47. package/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +5 -3
  48. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +986 -473
  49. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -1
  50. package/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +135 -39
  51. package/libs/africanies-theme/src/lib/mode-color.safelist.ts +2 -2
  52. package/libs/africanies-theme/src/lib/mode-color.service.spec.ts +9 -7
  53. package/libs/africanies-theme/src/lib/mode-color.service.ts +9 -7
  54. package/libs/africanies-theme/tailwind-preset.cjs +3 -3
  55. package/libs/africanies-ui/src/lib/avatar/avatar-menu.component.ts +25 -21
  56. package/libs/africanies-ui/src/lib/button/button.component.spec.ts +13 -8
  57. package/libs/africanies-ui/src/lib/chip/chip.component.ts +74 -6
  58. package/libs/africanies-ui/src/lib/layout/app-shell-header.component.ts +35 -12
  59. package/libs/africanies-ui/src/lib/layout/header-greeting.util.ts +2 -0
  60. package/libs/africanies-ui/src/lib/layout/header-weather.spec.ts +120 -9
  61. package/libs/africanies-ui/src/lib/layout/header-weather.ts +79 -18
  62. package/libs/africanies-ui/src/lib/navigation/segment/segment.component.ts +98 -22
  63. package/libs/africanies-ui/src/lib/navigation/shipping-mode-switch/shipping-mode-switch.component.spec.ts +15 -0
  64. package/libs/africanies-ui/src/lib/navigation/shipping-mode-switch/shipping-mode-switch.component.ts +195 -64
  65. package/libs/africanies-ui/src/lib/navigation/side-nav/africanies-side-nav.component.html +4 -2
  66. package/libs/africanies-ui/src/lib/navigation/side-nav/africanies-side-nav.component.ts +3 -1
  67. package/libs/africanies-ui/src/lib/table/table.component.spec.ts +3 -0
  68. package/libs/africanies-ui/src/lib/table/table.component.ts +254 -109
  69. package/package.json +1 -1
  70. package/tools/build-icon-sprite.mjs +1 -1
@@ -1,27 +1,37 @@
1
1
  import { NgTemplateOutlet } from '@angular/common';
2
2
  import {
3
+ afterNextRender,
3
4
  booleanAttribute,
4
5
  ChangeDetectionStrategy,
5
6
  Component,
6
7
  computed,
7
8
  contentChildren,
9
+ DestroyRef,
10
+ effect,
11
+ ElementRef,
8
12
  inject,
9
13
  input,
10
14
  numberAttribute,
11
15
  output,
12
16
  signal,
13
17
  TemplateRef,
18
+ viewChild,
14
19
  } from '@angular/core';
15
20
 
21
+ import { ShippingModeService } from '@africanies/africanies-core';
16
22
  import { AfricaniesIconComponent } from '@africanies/africanies-icons';
17
- import type { PaginationMetaModel } from '@africanies/africanies-models';
18
- import { ModeColorService } from '@africanies/africanies-theme';
23
+ import type {
24
+ PaginationMetaModel,
25
+ ShippingMode,
26
+ } from '@africanies/africanies-models';
19
27
 
20
28
  import { ButtonComponent } from '../button/button.component';
21
29
  import { EmptyStateComponent } from '../feedback/empty-state.component';
22
30
  import { ErrorIndicatorComponent } from '../feedback/error-indicator.component';
23
31
  import { ErrorStateComponent } from '../feedback/error-state.component';
24
32
  import { LoadingStateComponent } from '../feedback/loading-state.component';
33
+ import type { AfricaniesNavItem } from '../navigation/nav-item';
34
+ import { SegmentComponent } from '../navigation/segment/segment.component';
25
35
  import { PaginationComponent } from '../pagination/pagination.component';
26
36
  import { CellDefDirective } from './cell-def.directive';
27
37
  import { HeaderCellDefDirective } from './header-cell-def.directive';
@@ -35,8 +45,11 @@ import { TableColumn, TableSortChange } from './table-column';
35
45
  * `<ng-template africaniesCellDef="key">` templates. Columns without a matching
36
46
  * template fall back to rendering `row[key]` as plain text.
37
47
  *
38
- * Optional toolbar: top-left **Refresh** (`showRefresh` {@link refreshClick}).
39
- * Hidden while the body shows empty or error those states expose Retry instead.
48
+ * Optional toolbar: top-left **shipping mode** segment on its own row above
49
+ * Refresh / Filter / Export (on by default; `[showShippingMode]="false"` to
50
+ * hide) and **Refresh** (`showRefresh` →
51
+ * {@link refreshClick}). Refresh is hidden while
52
+ * the body shows empty or error — those states expose Retry instead.
40
53
  * While {@link refreshing} is true the rows stay on screen — the refresh icon
41
54
  * spins (do not swap to a blocking loader). Use {@link loading} when the page
42
55
  * of data is changing (pagination / size): rows stay on screen, a keep-rows
@@ -65,8 +78,9 @@ import { TableColumn, TableSortChange } from './table-column';
65
78
  * and never reorder local rows.
66
79
  *
67
80
  * **Expandable rows:** project `<ng-template africaniesRowDetail="Label" let-row>`
68
- * templates. A leading chevron column appears; the expanded panel renders each
69
- * label with its template value in a responsive grid.
81
+ * templates. A leading chevron column appears; the expanded panel sticks to the
82
+ * scrollport width (so it does not scroll horizontally with the columns) and
83
+ * lays out label/value pairs in a wrapping auto-fill grid.
70
84
  *
71
85
  * @typeParam T - Row record shape.
72
86
  *
@@ -121,6 +135,7 @@ import { TableColumn, TableSortChange } from './table-column';
121
135
  ErrorStateComponent,
122
136
  LoadingStateComponent,
123
137
  PaginationComponent,
138
+ SegmentComponent,
124
139
  ],
125
140
  host: {
126
141
  class: 'block w-full min-w-0',
@@ -134,69 +149,104 @@ import { TableColumn, TableSortChange } from './table-column';
134
149
  :host-context(.dark) tr.africanies-table-row:hover > td {
135
150
  background-color: #272729;
136
151
  }
152
+
153
+ /* Expand panel sticks to the scrollport (not the wide table). Width is
154
+ also set from measured clientWidth so it stays viewport-bound even
155
+ when container query units are flaky inside tables. */
156
+ :host .africanies-table-scrollport {
157
+ container-type: inline-size;
158
+ container-name: africanies-table;
159
+ }
160
+ :host .africanies-table-expand-panel {
161
+ position: sticky;
162
+ left: 0;
163
+ z-index: 1;
164
+ box-sizing: border-box;
165
+ width: 100cqi;
166
+ max-width: 100cqi;
167
+ overflow-x: hidden;
168
+ }
137
169
  `,
138
170
  template: `
139
171
  <div
140
172
  class="flex w-full min-w-0 flex-col gap-3"
141
173
  [attr.aria-busy]="loading() || refreshing() || null"
142
174
  >
143
- @if (showRefresh() || showFilter() || showExport()) {
144
- <div class="flex items-center justify-between gap-2">
145
- <div class="flex items-center gap-2">
146
- @if (showToolbarRefresh()) {
147
- <button
148
- africanies-button
149
- type="button"
150
- variant="flat"
151
- size="sm"
152
- [disabled]="loading() || refreshing()"
153
- [attr.aria-label]="refreshLabel()"
154
- (click)="refreshClick.emit()"
155
- >
156
- <africanies-icon
157
- name="refresh"
158
- [size]="16"
159
- [class]="refreshing() ? 'animate-spin' : ''"
160
- />
161
- {{ refreshLabel() }}
162
- </button>
163
- }
164
- </div>
165
- <div class="flex items-center gap-2">
166
- @if (showFilter()) {
167
- <button
168
- africanies-button
169
- type="button"
170
- variant="flat"
171
- size="sm"
172
- [attr.aria-label]="filterLabel()"
173
- (click)="filterClick.emit()"
174
- >
175
- <africanies-icon name="filter" [size]="16" />
176
- {{ filterLabel() }}
177
- @if (filterCount() > 0) {
178
- <span
179
- 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"
175
+ @if (
176
+ showShippingMode() ||
177
+ showRefresh() ||
178
+ showFilter() ||
179
+ showExport()
180
+ ) {
181
+ <div class="flex w-full min-w-0 flex-col gap-3">
182
+ @if (showShippingMode()) {
183
+ <africanies-segment
184
+ class="min-w-0 self-start"
185
+ [items]="shippingModeItems"
186
+ [activeId]="shippingModeActiveId()"
187
+ ariaLabel="Shipping mode"
188
+ (activeIdChange)="onShippingModeChange($event)"
189
+ />
190
+ }
191
+ @if (showToolbarRefresh() || showFilter() || showExport()) {
192
+ <div class="flex flex-wrap items-center justify-between gap-2">
193
+ <div class="flex min-w-0 flex-wrap items-center gap-2">
194
+ @if (showToolbarRefresh()) {
195
+ <button
196
+ africanies-button
197
+ type="button"
198
+ variant="flat"
199
+ size="sm"
200
+ [disabled]="loading() || refreshing()"
201
+ [attr.aria-label]="refreshLabel()"
202
+ (click)="refreshClick.emit()"
180
203
  >
181
- {{ filterCount() }}
182
- </span>
204
+ <africanies-icon
205
+ name="refresh"
206
+ [size]="16"
207
+ [class]="refreshing() ? 'animate-spin' : ''"
208
+ />
209
+ {{ refreshLabel() }}
210
+ </button>
183
211
  }
184
- </button>
185
- }
186
- @if (showExport()) {
187
- <button
188
- africanies-button
189
- type="button"
190
- variant="flat"
191
- size="sm"
192
- [attr.aria-label]="exportLabel()"
193
- (click)="exportClick.emit()"
194
- >
195
- <africanies-icon name="download" [size]="16" />
196
- {{ exportLabel() }}
197
- </button>
198
- }
199
- </div>
212
+ </div>
213
+ <div class="flex items-center gap-2">
214
+ @if (showFilter()) {
215
+ <button
216
+ africanies-button
217
+ type="button"
218
+ variant="flat"
219
+ size="sm"
220
+ [attr.aria-label]="filterLabel()"
221
+ (click)="filterClick.emit()"
222
+ >
223
+ <africanies-icon name="filter" [size]="16" />
224
+ {{ filterLabel() }}
225
+ @if (filterCount() > 0) {
226
+ <span
227
+ 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"
228
+ >
229
+ {{ filterCount() }}
230
+ </span>
231
+ }
232
+ </button>
233
+ }
234
+ @if (showExport()) {
235
+ <button
236
+ africanies-button
237
+ type="button"
238
+ variant="flat"
239
+ size="sm"
240
+ [attr.aria-label]="exportLabel()"
241
+ (click)="exportClick.emit()"
242
+ >
243
+ <africanies-icon name="download" [size]="16" />
244
+ {{ exportLabel() }}
245
+ </button>
246
+ }
247
+ </div>
248
+ </div>
249
+ }
200
250
  </div>
201
251
  }
202
252
 
@@ -211,13 +261,18 @@ import { TableColumn, TableSortChange } from './table-column';
211
261
  }
212
262
 
213
263
  <div
214
- class="relative min-w-0 w-full overflow-x-auto rounded-panel border border-border bg-surface dark:border-white/10 dark:bg-ink-surface"
215
- [class.lg:max-h-[min(70dvh,calc(100dvh-12rem))]]="stickyHeader()"
216
- [class.lg:overflow-auto]="stickyHeader()"
264
+ class="relative min-w-0 w-full overflow-hidden rounded-panel border border-border bg-surface dark:border-white/10 dark:bg-ink-surface"
217
265
  >
218
- <table
219
- class="w-max min-w-full table-auto border-separate border-spacing-0 bg-inherit text-left text-body text-ink dark:text-white"
266
+ <div
267
+ #scrollport
268
+ class="africanies-table-scrollport min-w-0 w-full overflow-x-auto"
269
+ [class.lg:max-h-[min(70dvh,calc(100dvh-12rem))]]="stickyHeader()"
270
+ [class.lg:overflow-auto]="stickyHeader() && !paginationLoading()"
271
+ [class.pointer-events-none]="paginationLoading()"
220
272
  >
273
+ <table
274
+ class="w-max min-w-full table-auto border-separate border-spacing-0 bg-inherit text-left text-body text-ink dark:text-white"
275
+ >
221
276
  <thead [class]="headerRowClass()">
222
277
  <tr>
223
278
  @if (isExpandable()) {
@@ -228,7 +283,7 @@ import { TableColumn, TableSortChange } from './table-column';
228
283
  @if (someRowsExpanded()) {
229
284
  <button
230
285
  type="button"
231
- class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-surface hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
286
+ 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"
232
287
  aria-label="Collapse all"
233
288
  (click)="collapseAllRows()"
234
289
  >
@@ -237,7 +292,7 @@ import { TableColumn, TableSortChange } from './table-column';
237
292
  } @else {
238
293
  <button
239
294
  type="button"
240
- class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-surface hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus disabled:cursor-not-allowed disabled:opacity-40 dark:text-neutral-400 dark:hover:bg-white/10 dark:hover:text-white"
295
+ 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"
241
296
  aria-label="Expand all"
242
297
  [disabled]="rowList().length === 0"
243
298
  (click)="expandAllRows()"
@@ -261,7 +316,7 @@ import { TableColumn, TableSortChange } from './table-column';
261
316
  } @else if (col.sortable) {
262
317
  <button
263
318
  type="button"
264
- class="inline-flex cursor-pointer items-center gap-1 rounded-sm font-medium text-neutral-600 hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus dark:text-neutral-400 dark:hover:text-white"
319
+ 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"
265
320
  (click)="onSortHeaderClick(col.key)"
266
321
  >
267
322
  <span>{{ col.header }}</span>
@@ -357,41 +412,52 @@ import { TableColumn, TableSortChange } from './table-column';
357
412
  [class.border-border]="isRowExpanded(row, i)"
358
413
  [class.dark:border-white/10]="isRowExpanded(row, i)"
359
414
  >
360
- <td class="p-0 align-top" [attr.colspan]="columns().length + 1">
415
+ <td class="p-0 align-top" [attr.colspan]="colSpan()">
416
+ <!-- Sticky must sit outside overflow-hidden or it pins
417
+ to the full table width and scrolls with the row. -->
361
418
  <div
362
- class="grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none"
363
- [style.grid-template-rows]="
364
- isRowExpanded(row, i) ? '1fr' : '0fr'
365
- "
366
- [attr.aria-hidden]="!isRowExpanded(row, i)"
367
- [class.pointer-events-none]="!isRowExpanded(row, i)"
419
+ class="africanies-table-expand-panel bg-surface-sunken dark:bg-white/[0.05]"
420
+ [style.width.px]="scrollportWidthPx() || null"
421
+ [style.maxWidth.px]="scrollportWidthPx() || null"
368
422
  >
369
423
  <div
370
- class="min-h-0 overflow-hidden border-t border-border-strong bg-surface-sunken dark:border-white/10 dark:bg-white/[0.05]"
424
+ class="grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none"
425
+ [style.grid-template-rows]="
426
+ isRowExpanded(row, i) ? '1fr' : '0fr'
427
+ "
428
+ [attr.aria-hidden]="!isRowExpanded(row, i)"
429
+ [class.pointer-events-none]="!isRowExpanded(row, i)"
371
430
  >
372
- <dl
373
- 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"
374
- [class.opacity-0]="!isRowExpanded(row, i)"
375
- [class.opacity-100]="isRowExpanded(row, i)"
431
+ <div
432
+ class="min-h-0 overflow-x-hidden overflow-y-hidden border-t border-border-strong dark:border-white/10"
376
433
  >
377
- @for (detail of rowDetailDefs(); track detail.label()) {
378
- <div class="min-w-0">
379
- <dt
380
- class="m-0 text-caption font-medium text-neutral-600 dark:text-neutral-400"
381
- >
382
- {{ detail.label() }}
383
- </dt>
384
- <dd
385
- class="m-0 mt-0.5 text-body-sm text-ink dark:text-white"
386
- >
387
- <ng-container
388
- [ngTemplateOutlet]="detail.template"
389
- [ngTemplateOutletContext]="cellContext(row)"
390
- />
391
- </dd>
392
- </div>
393
- }
394
- </dl>
434
+ <dl
435
+ 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"
436
+ [class.opacity-0]="!isRowExpanded(row, i)"
437
+ [class.opacity-100]="isRowExpanded(row, i)"
438
+ >
439
+ @for (
440
+ detail of rowDetailDefs();
441
+ track detail.label()
442
+ ) {
443
+ <div class="min-w-0">
444
+ <dt
445
+ class="m-0 text-caption font-medium text-neutral-600 dark:text-neutral-400"
446
+ >
447
+ {{ detail.label() }}
448
+ </dt>
449
+ <dd
450
+ class="m-0 mt-0.5 break-words text-body-sm text-ink dark:text-white"
451
+ >
452
+ <ng-container
453
+ [ngTemplateOutlet]="detail.template"
454
+ [ngTemplateOutletContext]="cellContext(row)"
455
+ />
456
+ </dd>
457
+ </div>
458
+ }
459
+ </dl>
460
+ </div>
395
461
  </div>
396
462
  </div>
397
463
  </td>
@@ -402,16 +468,21 @@ import { TableColumn, TableSortChange } from './table-column';
402
468
  }
403
469
  </tbody>
404
470
  </table>
471
+ </div>
405
472
  @if (paginationLoading()) {
406
473
  <div
407
- class="pointer-events-none absolute inset-0 z-[5] flex items-start justify-center bg-surface/70 px-4 pt-16 dark:bg-ink-950/70"
474
+ class="absolute inset-0 z-[5] bg-surface/70 dark:bg-ink-950/70"
408
475
  data-testid="africanies-table-keep-rows-loading"
409
476
  aria-hidden="true"
410
477
  >
411
- <africanies-loading-state
412
- mode="inline"
413
- [message]="loadingLabel()"
414
- />
478
+ <div
479
+ class="sticky top-0 flex h-[min(70dvh,100%)] items-center justify-center px-4"
480
+ >
481
+ <africanies-loading-state
482
+ mode="inline"
483
+ [message]="loadingLabel()"
484
+ />
485
+ </div>
415
486
  </div>
416
487
  }
417
488
  </div>
@@ -429,7 +500,51 @@ import { TableColumn, TableSortChange } from './table-column';
429
500
  `,
430
501
  })
431
502
  export class TableComponent<T = unknown> {
432
- protected readonly modeColor = inject(ModeColorService);
503
+ private readonly destroyRef = inject(DestroyRef);
504
+ protected readonly shipping = inject(ShippingModeService);
505
+
506
+ /** Local STN / SFN segment options for {@link showShippingMode}. */
507
+ protected readonly shippingModeItems: AfricaniesNavItem[] = [
508
+ { id: 'stn', label: 'Shipping to Nigeria' },
509
+ { id: 'sfn', label: 'Shipping from Nigeria' },
510
+ ];
511
+
512
+ /**
513
+ * Segment selection mirror of {@link ShippingModeService.mode}. Kept local
514
+ * so a denied {@link ShippingModeService.requestModeChange} can snap the
515
+ * pills back when the service mode did not change.
516
+ */
517
+ protected readonly shippingModeActiveId = signal<string>('sfn');
518
+
519
+ /** Scrollport that owns horizontal overflow for the main grid. */
520
+ private readonly scrollport =
521
+ viewChild<ElementRef<HTMLElement>>('scrollport');
522
+
523
+ /**
524
+ * Visible width of {@link scrollport}. Applied to the expand panel so
525
+ * details stay within the viewport and do not scroll with the columns.
526
+ */
527
+ protected readonly scrollportWidthPx = signal(0);
528
+
529
+ constructor() {
530
+ effect(() => {
531
+ this.shippingModeActiveId.set(this.shipping.mode());
532
+ });
533
+
534
+ afterNextRender(() => {
535
+ const el = this.scrollport()?.nativeElement;
536
+ if (!el) {
537
+ return;
538
+ }
539
+ const sync = (): void => {
540
+ this.scrollportWidthPx.set(el.clientWidth);
541
+ };
542
+ sync();
543
+ const observer = new ResizeObserver(sync);
544
+ observer.observe(el);
545
+ this.destroyRef.onDestroy(() => observer.disconnect());
546
+ });
547
+ }
433
548
 
434
549
  /**
435
550
  * When false, hides row expansion even if {@link RowDetailDefDirective}
@@ -470,6 +585,14 @@ export class TableComponent<T = unknown> {
470
585
  */
471
586
  readonly sort = input<TableSortChange | null>(null);
472
587
 
588
+ /**
589
+ * Show the Shipping to / from Nigeria segment above the table (top-left).
590
+ * On by default; set `[showShippingMode]="false"` to hide (e.g. modals).
591
+ * Writes through {@link ShippingModeService.requestModeChange} so feature
592
+ * guards still run.
593
+ */
594
+ readonly showShippingMode = input(true, { transform: booleanAttribute });
595
+
473
596
  /**
474
597
  * Show a Refresh button above the table (top-left) once rows are on screen.
475
598
  * Hidden during in-grid empty/error — those use Retry in the body. Host refetches
@@ -626,6 +749,27 @@ export class TableComponent<T = unknown> {
626
749
  () => this.showRefresh() && this.bodyKind() === 'rows',
627
750
  );
628
751
 
752
+ /**
753
+ * Applies a shipping-mode segment selection through
754
+ * {@link ShippingModeService.requestModeChange}.
755
+ *
756
+ * @param id - Segment id (`stn` / `sfn`) or null.
757
+ */
758
+ protected onShippingModeChange(id: string | null): void {
759
+ if (id !== 'stn' && id !== 'sfn') {
760
+ return;
761
+ }
762
+ this.shippingModeActiveId.set(id);
763
+ if (id === this.shipping.mode()) {
764
+ return;
765
+ }
766
+ this.shipping.requestModeChange(id as ShippingMode).subscribe((ok) => {
767
+ if (!ok) {
768
+ this.shippingModeActiveId.set(this.shipping.mode());
769
+ }
770
+ });
771
+ }
772
+
629
773
  protected readonly visibleRowIds = computed(() =>
630
774
  this.rowList().map((row, index) => this.rowId(row, index)),
631
775
  );
@@ -691,11 +835,12 @@ export class TableComponent<T = unknown> {
691
835
  }
692
836
 
693
837
  /**
694
- * Opaque mode soft fill for header cells (sticky-safe in dark mode).
695
- * @returns Soft solid background utilities.
838
+ * Opaque header fill (sticky-safe). Neutral chrome — not mode-accented.
839
+ * Light `#f0f2f5` / dark elevated ink surface.
840
+ * @returns Background utility classes.
696
841
  */
697
842
  protected headerSoftClass(): string {
698
- return this.modeColor.classes().softSolid;
843
+ return 'bg-[#f0f2f5] dark:bg-[#2a2c31]';
699
844
  }
700
845
 
701
846
  /**
@@ -777,7 +922,7 @@ export class TableComponent<T = unknown> {
777
922
  : 'border-b ';
778
923
  return (
779
924
  border +
780
- '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 ' +
925
+ '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 ' +
781
926
  this.headerSoftClass() +
782
927
  ' ' +
783
928
  this.stickySurfaceClass(col, 'head') +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@africanies/angular-web-sdk",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "packageManager": "pnpm@11.24.0",
5
5
  "license": "MIT",
6
6
  "contributors": [
@@ -85,7 +85,7 @@ function parseSvg(svg, iconName) {
85
85
  */
86
86
  const PRESERVED_FILLS = new Set([
87
87
  '#ef8833',
88
- '#1cbd5d',
88
+ '#26bd5d',
89
89
  '#26a4f0',
90
90
  ]);
91
91