@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
@@ -0,0 +1,275 @@
1
+ import { countryFlagUrl } from '@africanies/africanies-core';
2
+
3
+ import {
4
+ PHONE_DIAL_CODES,
5
+ PHONE_MAX_NATIONAL_DIGITS,
6
+ PHONE_MIN_NATIONAL_DIGITS,
7
+ } from './phone-codes';
8
+ import type { PhoneCountryOption, PhoneNumberValue } from './phone-input.types';
9
+
10
+ const regionNames =
11
+ typeof Intl !== 'undefined' && 'DisplayNames' in Intl
12
+ ? new Intl.DisplayNames(['en'], { type: 'region' })
13
+ : null;
14
+
15
+ /** Digits only from a phone draft. */
16
+ export function phoneDigitsOnly(value: string | null | undefined): string {
17
+ return (value ?? '').replace(/\D/g, '');
18
+ }
19
+
20
+ /** Normalize ISO2 to uppercase two letters, or `''`. */
21
+ export function normalizePhoneIso2(iso2: string | null | undefined): string {
22
+ const code = (iso2 ?? '').trim().toUpperCase();
23
+ return /^[A-Z]{2}$/.test(code) ? code : '';
24
+ }
25
+
26
+ /** Dial code for an ISO2 (with `+`), or `''` when unknown. */
27
+ export function dialCodeForIso2(iso2: string | null | undefined): string {
28
+ const code = normalizePhoneIso2(iso2);
29
+ return code ? (PHONE_DIAL_CODES[code] ?? '') : '';
30
+ }
31
+
32
+ /** English country name from ISO2 (falls back to the code). */
33
+ export function phoneCountryName(iso2: string): string {
34
+ const code = normalizePhoneIso2(iso2);
35
+ if (!code) {
36
+ return '';
37
+ }
38
+ try {
39
+ return regionNames?.of(code) ?? code;
40
+ } catch {
41
+ return code;
42
+ }
43
+ }
44
+
45
+ /** All dial-code countries for the picker (sorted by name). */
46
+ export function phoneCountryOptions(): PhoneCountryOption[] {
47
+ return Object.keys(PHONE_DIAL_CODES)
48
+ .map((iso2) => {
49
+ const dialCode = PHONE_DIAL_CODES[iso2]!;
50
+ return {
51
+ iso2,
52
+ dialCode,
53
+ name: phoneCountryName(iso2),
54
+ flagUrl: countryFlagUrl(iso2, { width: 40 }),
55
+ };
56
+ })
57
+ .sort((a, b) => a.name.localeCompare(b.name));
58
+ }
59
+
60
+ /**
61
+ * Filter dial-code countries by name, ISO2, or dial digits.
62
+ * Digit matching is skipped when the query has no digits — otherwise
63
+ * `''.includes('')` would keep every country for letter-only searches.
64
+ */
65
+ export function filterPhoneCountries(
66
+ countries: readonly PhoneCountryOption[],
67
+ query: string,
68
+ ): PhoneCountryOption[] {
69
+ const q = query.trim().toLowerCase();
70
+ if (!q) {
71
+ return [...countries];
72
+ }
73
+ const qDigits = phoneDigitsOnly(q);
74
+ return countries.filter((row) => {
75
+ if (
76
+ row.name.toLowerCase().includes(q) ||
77
+ row.iso2.toLowerCase().includes(q) ||
78
+ row.dialCode.toLowerCase().includes(q)
79
+ ) {
80
+ return true;
81
+ }
82
+ return qDigits.length > 0
83
+ ? phoneDigitsOnly(row.dialCode).includes(qDigits)
84
+ : false;
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Match an E.164 / international string to the longest known dial code.
90
+ * Prefers `hintIso2` when several countries share the same dial code (e.g. `+1`).
91
+ */
92
+ export function matchDialCode(
93
+ e164OrInternational: string,
94
+ hintIso2?: string | null,
95
+ ): { iso2: string; dialCode: string; nationalDigits: string } | null {
96
+ const digits = phoneDigitsOnly(e164OrInternational);
97
+ if (!digits) {
98
+ return null;
99
+ }
100
+
101
+ const hint = normalizePhoneIso2(hintIso2);
102
+ if (hint) {
103
+ const hintDial = PHONE_DIAL_CODES[hint];
104
+ if (hintDial) {
105
+ const hintDigits = phoneDigitsOnly(hintDial);
106
+ if (digits.startsWith(hintDigits)) {
107
+ return {
108
+ iso2: hint,
109
+ dialCode: hintDial,
110
+ nationalDigits: digits.slice(hintDigits.length),
111
+ };
112
+ }
113
+ }
114
+ }
115
+
116
+ let best: { iso2: string; dialCode: string; nationalDigits: string } | null =
117
+ null;
118
+ for (const [iso2, dialCode] of Object.entries(PHONE_DIAL_CODES)) {
119
+ const dialDigits = phoneDigitsOnly(dialCode);
120
+ if (!digits.startsWith(dialDigits)) {
121
+ continue;
122
+ }
123
+ const nationalDigits = digits.slice(dialDigits.length);
124
+ if (
125
+ !best ||
126
+ dialDigits.length > phoneDigitsOnly(best.dialCode).length
127
+ ) {
128
+ best = { iso2, dialCode, nationalDigits };
129
+ }
130
+ }
131
+ return best;
132
+ }
133
+
134
+ /**
135
+ * Strip a leading dial code from a stored phone (classic `removeDialCode`).
136
+ * Returns national digits for prefilling the input.
137
+ */
138
+ export function removeDialCode(
139
+ phone: string | null | undefined,
140
+ hintIso2?: string | null,
141
+ ): string {
142
+ const raw = (phone ?? '').trim();
143
+ if (!raw) {
144
+ return '';
145
+ }
146
+ const matched = matchDialCode(raw, hintIso2);
147
+ if (matched) {
148
+ return matched.nationalDigits;
149
+ }
150
+ return phoneDigitsOnly(raw);
151
+ }
152
+
153
+ /** Group national digits for readable display (… + last 4). */
154
+ export function formatNationalDigits(digits: string): string {
155
+ const clean = phoneDigitsOnly(digits).slice(0, PHONE_MAX_NATIONAL_DIGITS);
156
+ if (!clean) {
157
+ return '';
158
+ }
159
+ if (clean.length <= 4) {
160
+ return clean;
161
+ }
162
+ const end = clean.slice(-4);
163
+ const head = clean.slice(0, -4).replace(/(\d{3})(?=\d)/g, '$1 ').trim();
164
+ return `${head} ${end}`.trim();
165
+ }
166
+
167
+ /**
168
+ * Build the structured value (or `null` when empty).
169
+ */
170
+ export function buildPhoneNumberValue(
171
+ iso2: string,
172
+ nationalDraft: string,
173
+ ): PhoneNumberValue | null {
174
+ const countryCode = normalizePhoneIso2(iso2);
175
+ const dialCode = dialCodeForIso2(countryCode);
176
+ if (!countryCode || !dialCode) {
177
+ return null;
178
+ }
179
+
180
+ const nationalDigits = phoneDigitsOnly(nationalDraft).slice(
181
+ 0,
182
+ PHONE_MAX_NATIONAL_DIGITS,
183
+ );
184
+ if (!nationalDigits) {
185
+ return null;
186
+ }
187
+
188
+ const number = formatNationalDigits(nationalDigits);
189
+ const e164Number = `${dialCode}${nationalDigits}`;
190
+ const internationalNumber = `${dialCode} ${number}`.trim();
191
+ // Trunk-prefix `0` for national display when digits omit it (classic ngx shape).
192
+ const nationalNumber = nationalDigits.startsWith('0')
193
+ ? number
194
+ : `0${number}`;
195
+
196
+ return {
197
+ number,
198
+ internationalNumber,
199
+ nationalNumber,
200
+ e164Number,
201
+ countryCode,
202
+ dialCode,
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Canonical outbound phone string — prefer E.164 from a structured value.
208
+ */
209
+ export function phoneToE164(
210
+ phone: PhoneNumberValue | string | null | undefined,
211
+ ): string {
212
+ if (phone == null) {
213
+ return '';
214
+ }
215
+ if (typeof phone === 'string') {
216
+ const digits = phoneDigitsOnly(phone);
217
+ if (!digits) {
218
+ return '';
219
+ }
220
+ return phone.trim().startsWith('+') ? `+${digits}` : phone.trim();
221
+ }
222
+ return phone.e164Number?.trim() || '';
223
+ }
224
+
225
+ /**
226
+ * Soft validity: has dial code + enough national digits (not full libphonenumber).
227
+ */
228
+ export function isPhoneNumberComplete(
229
+ phone: PhoneNumberValue | null | undefined,
230
+ ): boolean {
231
+ if (!phone?.e164Number) {
232
+ return false;
233
+ }
234
+ const national = phoneDigitsOnly(phone.number);
235
+ return (
236
+ national.length >= PHONE_MIN_NATIONAL_DIGITS &&
237
+ national.length <= PHONE_MAX_NATIONAL_DIGITS
238
+ );
239
+ }
240
+
241
+ /**
242
+ * Split an incoming control value (E.164 string or structured) into UI state.
243
+ */
244
+ export function parsePhoneControlValue(
245
+ value: string | PhoneNumberValue | null | undefined,
246
+ fallbackIso2: string,
247
+ ): { iso2: string; nationalDigits: string } {
248
+ const fallback = normalizePhoneIso2(fallbackIso2) || 'US';
249
+
250
+ if (value == null || value === '') {
251
+ return { iso2: fallback, nationalDigits: '' };
252
+ }
253
+
254
+ if (typeof value === 'object') {
255
+ const iso2 = normalizePhoneIso2(value.countryCode) || fallback;
256
+ const national =
257
+ phoneDigitsOnly(value.number) ||
258
+ phoneDigitsOnly(value.nationalNumber) ||
259
+ removeDialCode(value.e164Number, iso2);
260
+ return { iso2, nationalDigits: national };
261
+ }
262
+
263
+ const matched = matchDialCode(value, fallback);
264
+ if (matched) {
265
+ return {
266
+ iso2: matched.iso2,
267
+ nationalDigits: matched.nationalDigits,
268
+ };
269
+ }
270
+
271
+ return {
272
+ iso2: fallback,
273
+ nationalDigits: phoneDigitsOnly(value),
274
+ };
275
+ }
@@ -6,6 +6,7 @@ import { DatePickerComponent } from '../forms/date-picker';
6
6
  import { FileUploadComponent } from '../forms/file-upload';
7
7
  import { NumberInputComponent } from '../forms/number-input';
8
8
  import { OtpInputComponent } from '../forms/otp-input';
9
+ import { PhoneInputComponent } from '../forms/phone-input';
9
10
  import { RadioComponent } from '../forms/radio';
10
11
  import { SelectComponent } from '../forms/select';
11
12
  import { TextInputComponent } from '../forms/text-input';
@@ -24,6 +25,7 @@ const FORM_CONTROLS = [
24
25
  DatePickerComponent,
25
26
  FileUploadComponent,
26
27
  OtpInputComponent,
28
+ PhoneInputComponent,
27
29
  ] as const;
28
30
 
29
31
  /**
@@ -70,8 +70,9 @@ const DEFAULT_LINK_ACTIVE: IsActiveMatchOptions = {
70
70
  changeDetection: ChangeDetectionStrategy.OnPush,
71
71
  imports: [AfricaniesIconComponent],
72
72
  styles: `
73
+ /* Track p-1 (0.25×2) + 0.25 gap between pills — keep right inset equal to left. */
73
74
  :host .africanies-segment-thumb {
74
- width: calc((100% - 0.5rem) / 2);
75
+ width: calc((100% - 0.75rem) / 2);
75
76
  }
76
77
  `,
77
78
  template: `
@@ -59,4 +59,19 @@ describe('ShippingModeSwitchComponent', () => {
59
59
  expect(shipping.mode()).toBe('stn');
60
60
  expect(toNigeria.getAttribute('aria-checked')).toBe('true');
61
61
  });
62
+
63
+ it('should show the STN/SFN chip only when the mode panel is collapsed', () => {
64
+ fixture.componentRef.setInput('panelOpen', true);
65
+ fixture.detectChanges();
66
+ expect(fixture.nativeElement.textContent).not.toContain('SFN');
67
+
68
+ fixture.componentRef.setInput('panelOpen', false);
69
+ fixture.detectChanges();
70
+ expect(fixture.nativeElement.textContent).toContain('SFN');
71
+
72
+ shipping.setMode('stn');
73
+ fixture.detectChanges();
74
+ expect(fixture.nativeElement.textContent).toContain('STN');
75
+ expect(fixture.nativeElement.textContent).not.toContain('SFN');
76
+ });
62
77
  });
@@ -128,9 +128,12 @@ const SHIPPING_MODE_GLYPH = `M88.42,68.75a48.8,48.8,0,0,1-4.74,11.1h9.89a7.67,7.
128
128
  >
129
129
  Shipping mode
130
130
  </p>
131
- <africanies-chip [variant]="modeChipVariant()" size="sm">
132
- {{ modeChipLabel() }}
133
- </africanies-chip>
131
+ <!-- STN / SFN chip only when the tiles are collapsed — open panel already shows both modes. -->
132
+ @if (!panelOpen()) {
133
+ <africanies-chip [variant]="modeChipVariant()" size="sm">
134
+ {{ modeChipLabel() }}
135
+ </africanies-chip>
136
+ }
134
137
  <span class="min-w-0 flex-1" aria-hidden="true"></span>
135
138
  <button
136
139
  type="button"
@@ -45,8 +45,9 @@ import { TableColumn, TableSortChange } from './table-column';
45
45
  * `<ng-template africaniesCellDef="key">` templates. Columns without a matching
46
46
  * template fall back to rendering `row[key]` as plain text.
47
47
  *
48
- * Optional toolbar: top-left **shipping mode** segment (on by default;
49
- * `[showShippingMode]="false"` to hide) and **Refresh** (`showRefresh` →
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` →
50
51
  * {@link refreshClick}). Refresh is hidden while
51
52
  * the body shows empty or error — those states expose Retry instead.
52
53
  * While {@link refreshing} is true the rows stay on screen — the refresh icon
@@ -177,71 +178,75 @@ import { TableColumn, TableSortChange } from './table-column';
177
178
  showFilter() ||
178
179
  showExport()
179
180
  ) {
180
- <div class="flex flex-wrap items-center justify-between gap-2">
181
- <div class="flex min-w-0 flex-wrap items-center gap-2">
182
- @if (showShippingMode()) {
183
- <africanies-segment
184
- class="min-w-0"
185
- [items]="shippingModeItems"
186
- [activeId]="shippingModeActiveId()"
187
- ariaLabel="Shipping mode"
188
- (activeIdChange)="onShippingModeChange($event)"
189
- />
190
- }
191
- @if (showToolbarRefresh()) {
192
- <button
193
- africanies-button
194
- type="button"
195
- variant="flat"
196
- size="sm"
197
- [disabled]="loading() || refreshing()"
198
- [attr.aria-label]="refreshLabel()"
199
- (click)="refreshClick.emit()"
200
- >
201
- <africanies-icon
202
- name="refresh"
203
- [size]="16"
204
- [class]="refreshing() ? 'animate-spin' : ''"
205
- />
206
- {{ refreshLabel() }}
207
- </button>
208
- }
209
- </div>
210
- <div class="flex items-center gap-2">
211
- @if (showFilter()) {
212
- <button
213
- africanies-button
214
- type="button"
215
- variant="flat"
216
- size="sm"
217
- [attr.aria-label]="filterLabel()"
218
- (click)="filterClick.emit()"
219
- >
220
- <africanies-icon name="filter" [size]="16" />
221
- {{ filterLabel() }}
222
- @if (filterCount() > 0) {
223
- <span
224
- 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"
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()"
225
203
  >
226
- {{ filterCount() }}
227
- </span>
204
+ <africanies-icon
205
+ name="refresh"
206
+ [size]="16"
207
+ [class]="refreshing() ? 'animate-spin' : ''"
208
+ />
209
+ {{ refreshLabel() }}
210
+ </button>
228
211
  }
229
- </button>
230
- }
231
- @if (showExport()) {
232
- <button
233
- africanies-button
234
- type="button"
235
- variant="flat"
236
- size="sm"
237
- [attr.aria-label]="exportLabel()"
238
- (click)="exportClick.emit()"
239
- >
240
- <africanies-icon name="download" [size]="16" />
241
- {{ exportLabel() }}
242
- </button>
243
- }
244
- </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
+ }
245
250
  </div>
246
251
  }
247
252
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@africanies/angular-web-sdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "packageManager": "pnpm@11.24.0",
5
5
  "license": "MIT",
6
6
  "contributors": [
@@ -1,85 +0,0 @@
1
- # @africanies/africanies-core
2
-
3
- API and app-wiring layer for the AFRICANIES Web SDK: SDK config, HTTP client,
4
- shipping mode, mode-region metadata, TanStack Query default helpers, and
5
- route-driven overlay registration.
6
-
7
- ## Install peers
8
-
9
- ```bash
10
- # Published on npm (@africanies scope)
11
- npm install @africanies/africanies-core @africanies/africanies-models @africanies/africanies-storage
12
- ```
13
-
14
- Peer dependencies: `@angular/common`, `@angular/core`, `@angular/router`,
15
- `rxjs`, `@africanies/africanies-models`, `@africanies/africanies-storage`.
16
-
17
- ## Quick start
18
-
19
- ```ts
20
- import { ApplicationConfig } from '@angular/core';
21
- import {
22
- provideAfricaniesSdk,
23
- provideAfricaniesHttpClient,
24
- provideOverlayRoutes,
25
- createAfricaniesQueryClientDefaults,
26
- } from '@africanies/africanies-core';
27
-
28
- export const appConfig: ApplicationConfig = {
29
- providers: [
30
- provideAfricaniesSdk({ baseUrl: 'https://api.example.com', timeout: 30_000 }),
31
- provideAfricaniesHttpClient(),
32
- // optional extras:
33
- // provideAfricaniesHttpClient({ interceptors: [correlationInterceptor] }),
34
- // optional: provideOverlayRoutes([{ paramKey: 'modal', routes: { ... } }]),
35
- ],
36
- };
37
-
38
- // TanStack Query (app dependency — pin exact experimental version):
39
- // new QueryClient({ defaultOptions: createAfricaniesQueryClientDefaults() })
40
- ```
41
-
42
- ## Modules
43
-
44
- | Area | Exports |
45
- |------|---------|
46
- | Config | `provideAfricaniesSdk`, `AFRICANIES_SDK_CONFIG`, `httpToasts` (`'off'` \| `'errors'` \| `'all'`) |
47
- | Auth | `AuthService` (`forgot`), `isValidEmail`, `AuthTokenService` (`set` / `clear` / `get`) |
48
- | Shipping | `ShippingModeService` (tab-scoped via `sessionStorage`), `shipmentModeInterceptor`, per-request `ApiRequestOptions.shippingMode` / `withShippingMode` |
49
- | HTTP | `provideAfricaniesHttpClient`, `ApiClient` (auto-tags `withToast` from config / `ApiRequestOptions.toast`; optional `shippingMode` per call; HTTP failures → `Error` with formatted `.message`), `withToast`, `withShippingMode`, `httpToastInterceptor`, `normalize` (lifts Laravel validation bags into `errors` + joined `message`), `formatApiErrorMessage` (used internally; rare for hosts), `fieldErrorsMap`, `authInterceptor` |
50
- | Query | `createAfricaniesQueryClientDefaults`, `provideAfricaniesQueryDefaults`, `listFetchKind` (body loader on first load / STN↔SFN; keep rows on focus / Refresh / pager) |
51
- | Mode | `ModeConfigService`, `provideModeConfig`, `MODE_CONFIG_PATH` |
52
- | Country | `CountryService`, `countryFlagUrl` / `mapCountrySelectOptions` (flagcdn.com), `COUNTRY_READ_PATH`, mappers |
53
- | Service | `ServiceService` (`readPage` / `readAll` / `readById`), `SERVICE_READ_PATH` (`/public/service/read`), mappers |
54
- | Document | `DocumentService` (`readPage` / `readAll` / `readById`), `DOCUMENT_READ_PATH` (`/public/document/read`) — preview via `file_ref` on by-id |
55
- | Plan | `PlanService` (`readPage` / `readAll` / `readById`), `PLAN_READ_PATH` (`/public/plan/read`), mappers |
56
- | Currency | `CurrencyService` (`readPage` / `readAll` / `readById`, `create` / `update` / `remove`), `CURRENCY_*_PATH`, mappers |
57
- | Payment method | `PaymentMethodService` (`readPage` / `readAll` / `readById`, `update`), `PAYMENT_METHOD_READ_PATH` / `PAYMENT_METHOD_UPDATE_PATH`, mappers |
58
- | Shipment method | `ShipmentMethodService` (`readPage` / `readAll` / `readById`), `SHIPMENT_METHOD_READ_PATH`, mappers |
59
- | Warehouse | `WarehouseService` (`readPage` / `readAll` / `readById`), `WAREHOUSE_READ_PATH`, mappers |
60
- | Zone | `ZoneService` (`readPage` / `readAll` / `readById`), `ZONE_READ_PATH`, mappers |
61
- | Product | `ProductService` (`readPage` / `readAll` / `readById`), `PRODUCT_READ_PATH`, mappers |
62
- | User | `UserService` (`me`, `changePassword`, `logoutFromAllSessions`), `USER_PATH`, `mapUser` (bare `/user` body; needs `AuthTokenService.set`) |
63
- | Notification | `NotificationService` (`readPage` / `readAll` / `readOne`), `NOTIFICATION_READ_PATH`, `mapNotification` / `mapNotificationInboxItem` |
64
- | File | `FileService` (`read` / `readMultiple` → `POST /file/read`), `FILE_READ_PATH`, `mapFileRead` / `mapFileReadList` |
65
- | Overlay | `provideOverlayRoutes`, `RouteOverlayService`, `MODAL_SERVICE`, `DRAWER_SERVICE` |
66
- | Filters | `FilterOptionsResolver` (`resolveField` lazy in drawer), `mergeFilterOptionLists`, warehouse/method mappers |
67
- | Browser | `copyToClipboard`, `downloadCsv` / `csvCell` (UTF-8 CSV download) |
68
-
69
- List-style GETs follow `ResourceId`: `null` (paginated), `'all'` (full dump), `number` (single).
70
-
71
- | Filter `optionsSource` | SDK resolver |
72
- |------------------------|--------------|
73
- | `warehouses` | Lazy `FilterOptionsResolver.resolveField` → `WarehouseService.readAll()` |
74
- | `shipmentMethods` | Lazy `FilterOptionsResolver.resolveField` → `ShipmentMethodService.readAll()` |
75
- | `shipmentManifests` | Host `optionLists` (no built-in service yet) |
76
-
77
- `MODAL_SERVICE` / `DRAWER_SERVICE` are tokens only — implementations ship in
78
- `@africanies/africanies-ui` to avoid circular dependencies.
79
-
80
- ## Build / test
81
-
82
- ```bash
83
- npx nx build africanies-core
84
- npx nx test africanies-core
85
- ```