Skip to content

Commit 1a4e723

Browse files
Merge remote-tracking branch 'origin/develop' into extensions/skeleton-loader
2 parents a4742f9 + 85db470 commit 1a4e723

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+5523
-10670
lines changed

libs/extensions/angular/.storybook/preview.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { applicationConfig, Preview } from '@storybook/angular';
33
import { setCompodocJson } from '@storybook/addon-docs/angular';
44

55
import { KirbyIonicModule } from '@kirbydesign/designsystem/kirby-ionic-module';
6+
import { provideKirbyExtensionsLocalizationToken } from '@kirbydesign/extensions-angular/localization';
7+
68
import { defaultParameters } from 'tools/storybook-config/shared-config';
79

810
import docJson from '../docs/documentation.json';
@@ -20,7 +22,15 @@ const preview: Preview = {
2022
},
2123
decorators: [
2224
applicationConfig({
23-
providers: [importProvidersFrom([KirbyIonicModule])],
25+
providers: [
26+
importProvidersFrom([KirbyIonicModule]),
27+
provideKirbyExtensionsLocalizationToken(() => ({
28+
nativeCurrency: 'DKK',
29+
defaultLang: 'da',
30+
countryCode: '+45',
31+
timeZone: 'Europe/Copenhagen',
32+
})),
33+
],
2434
}),
2535
],
2636
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"lib": {
3+
"entryFile": "src/index.ts"
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { AccountNumber } from './account-number.model';
2+
3+
export function formatAccountNumber(value: AccountNumber): string {
4+
return `${value.regNo.padStart(4, '0')} ${value.accountNo.replace(/^0+/, '')}`;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface AccountNumber {
2+
regNo: string;
3+
accountNo: string;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
2+
3+
import { AccountNumber } from './account-number.model';
4+
import { AccountNumberPipe } from './account-number.pipe';
5+
6+
describe('AccountNumberPipe', () => {
7+
let spectator: SpectatorService<AccountNumberPipe>;
8+
9+
const createPipe = createServiceFactory({
10+
service: AccountNumberPipe,
11+
});
12+
13+
beforeEach(() => {
14+
spectator = createPipe();
15+
});
16+
17+
describe('transform', () => {
18+
it('should format an account number with a space', () => {
19+
const accountNumber: AccountNumber = {
20+
regNo: '1234',
21+
accountNo: '1234567890',
22+
};
23+
expect(spectator.service.transform(accountNumber)).toEqual('1234 1234567890');
24+
});
25+
26+
it('should pad reg. number to 4 digits (with "0" - zeros)', () => {
27+
const accountNumber: AccountNumber = {
28+
regNo: '1',
29+
accountNo: '1234567890',
30+
};
31+
expect(spectator.service.transform(accountNumber)).toEqual('0001 1234567890');
32+
});
33+
34+
it('should remove leading zeroes from account number', () => {
35+
const accountNumber: AccountNumber = {
36+
regNo: '1234',
37+
accountNo: '000100',
38+
};
39+
expect(spectator.service.transform(accountNumber)).toEqual('1234 100');
40+
});
41+
});
42+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
3+
import { AccountNumber } from './account-number.model';
4+
import { formatAccountNumber } from './account-number-service-formatter';
5+
6+
/**
7+
* Pipe that formats a {@link AccountNumber}-object to a common format.
8+
*/
9+
@Pipe({
10+
name: 'accountNumber',
11+
standalone: true,
12+
})
13+
export class AccountNumberPipe implements PipeTransform {
14+
/**
15+
* Formats the {@link AccountNumber} to a common format.
16+
*
17+
* @param value the {@link AccountNumber} to format
18+
*/
19+
transform(value: AccountNumber): string | undefined {
20+
return formatAccountNumber(value);
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Component, Input } from '@angular/core';
2+
import { AccountNumber, AccountNumberPipe } from '@kirbydesign/extensions-angular/localization';
3+
import { type Meta, moduleMetadata, StoryObj } from '@storybook/angular';
4+
5+
@Component({
6+
template: `
7+
{{ myAccountNumber | accountNumber }}
8+
`,
9+
selector: 'extensions-account-number-example',
10+
standalone: true,
11+
imports: [AccountNumberPipe],
12+
})
13+
class AccountNumberExampleComponent {
14+
/**
15+
* An example `AccountNumber` to be formatted.
16+
*/
17+
@Input() myAccountNumber!: AccountNumber;
18+
19+
/**
20+
* Name of pipe to use in the template.
21+
*/
22+
@Input() accountNumber!: AccountNumberPipe;
23+
}
24+
25+
/**
26+
* The Account Number Pipe formats an `AccountNumber` to a common format.
27+
*
28+
* The registration number is padded with leading zeros if necessary, and any leading zeros in the account number are removed.
29+
* A whitespace is added between the registration number and the account number.
30+
*/
31+
const meta: Meta<AccountNumberExampleComponent> = {
32+
component: AccountNumberExampleComponent,
33+
title: 'Pipes/Localization/Formatting',
34+
decorators: [
35+
moduleMetadata({
36+
imports: [AccountNumberPipe],
37+
}),
38+
],
39+
tags: ['!autodocs', 'dev'],
40+
argTypes: {
41+
accountNumber: { control: false },
42+
},
43+
};
44+
45+
export default meta;
46+
type Story = StoryObj<AccountNumberExampleComponent>;
47+
48+
export const Account_Number: Story = {
49+
args: {
50+
myAccountNumber: { regNo: '987', accountNo: '1234567890' },
51+
},
52+
parameters: {
53+
docs: {
54+
source: {
55+
language: 'tsx', // Using tsx here to get better syntax highlighting
56+
code: `<p>
57+
{{ myAccountNumber /* e.g. { regNo: '987', accountNo: '1234567890' } */ | accountNumber }}
58+
</p> `,
59+
},
60+
},
61+
},
62+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './account-number.model';
2+
export * from './account-number.pipe';
3+
export * from './account-number-service-formatter';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { formatNumber } from '@angular/common';
2+
3+
import { Amount } from './amount.model';
4+
5+
export function formatAmount(
6+
amount: Amount,
7+
amountServiceConfiguration: AmountServiceConfiguration,
8+
locale: string,
9+
nativeCurrency: string
10+
) {
11+
const config = deriveConfiguration(amountServiceConfiguration);
12+
13+
let formattedAmount = formatNumber(amount && amount.amount, locale, config.digitsInfo);
14+
15+
if (config.stripSign) {
16+
formattedAmount = formattedAmount.replace('-', '').trim();
17+
}
18+
19+
const currencyCodeToAppend = deriveCurrencyCode(config, amount, nativeCurrency);
20+
21+
if (!currencyCodeToAppend) {
22+
return formattedAmount;
23+
}
24+
if (config.currencyCodePosition === 'postfix') {
25+
return formattedAmount + ' ' + currencyCodeToAppend;
26+
} else {
27+
return currencyCodeToAppend + ' ' + formattedAmount;
28+
}
29+
}
30+
31+
export function deriveCurrencyCode(
32+
config: AmountServiceConfiguration,
33+
amount: Amount,
34+
nativeCurrency: string
35+
) {
36+
let currencyCodeToAppend;
37+
38+
if (config.showCurrencyCode) {
39+
if (config.showCurrencyCode === 'alwaysShowCurrency') {
40+
currencyCodeToAppend = amount && amount.currencyCode;
41+
} else if (config.showCurrencyCode === 'showForeignCurrency') {
42+
currencyCodeToAppend =
43+
amount && amount.currencyCode !== nativeCurrency ? amount.currencyCode : '';
44+
}
45+
}
46+
47+
return currencyCodeToAppend || '';
48+
}
49+
50+
export function deriveConfiguration(configuration: AmountServiceConfiguration) {
51+
const config: AmountServiceConfiguration = {
52+
showCurrencyCode: '',
53+
digitsInfo: '1.2-2',
54+
stripSign: false,
55+
};
56+
57+
return Object.assign({}, config, configuration);
58+
}
59+
60+
export type ShowCurrencyCode = '' | 'alwaysShowCurrency' | 'showForeignCurrency';
61+
export type CurrencyCodePosition = '' | 'prefix' | 'postfix';
62+
63+
export interface AmountServiceConfiguration {
64+
/**
65+
* - '' - don't output CurrencyCode
66+
* - 'alwaysShowCurrency' - always shows CurrencyCode, regardless of presentation currency
67+
* - 'showForeignCurrency' - only show CurrencyCode if it differs from the presentation currency
68+
*/
69+
showCurrencyCode: ShowCurrencyCode;
70+
/**
71+
* The position of the currency code in the formatted amount
72+
* - 'postfix' - output CurrencyCode after the formatted amount, eg. 1.234,56 EUR
73+
* - 'prefix' - output CurrencyCode before the formatted amount, eg. DKK 1.234,56
74+
*/
75+
currencyCodePosition?: CurrencyCodePosition;
76+
/**
77+
* A string that represents the format of the number. Learn more about the format here: https://angular.io/api/common/DecimalPipe#parameters
78+
*/
79+
digitsInfo?: string;
80+
/**
81+
* Remove the minus sign from the formatted amount and trim any leading or trailing whitespace.
82+
*/
83+
stripSign?: boolean;
84+
/**
85+
* The string to return if the amount is empty
86+
*/
87+
returnValueOnEmptyAmount?: string;
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export interface Amount {
2+
/**
3+
* The monetary value
4+
* @min -999999999
5+
* @max 999999999
6+
* @example [100]
7+
*/
8+
amount: number;
9+
10+
/**
11+
* The currency in which the value is denominated
12+
* @example [DKK]
13+
*/
14+
currencyCode: string;
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {
2+
createServiceFactory,
3+
mockProvider,
4+
SpectatorService,
5+
SpyObject,
6+
} from '@ngneat/spectator/jest';
7+
8+
import { AmountPipe } from './amount.pipe';
9+
import { AmountService } from './amount.service';
10+
import { AmountServiceConfiguration } from './amount-service-formatter';
11+
12+
describe('AmountPipe', () => {
13+
let spectator: SpectatorService<AmountPipe>;
14+
let amountService: SpyObject<AmountService>;
15+
16+
const createPipe = createServiceFactory({
17+
service: AmountPipe,
18+
providers: [mockProvider(AmountService)],
19+
});
20+
21+
beforeEach(() => {
22+
spectator = createPipe();
23+
amountService = spectator.inject(AmountService);
24+
});
25+
26+
it('should call amount service', () => {
27+
const amount = { amount: 2000.01, currencyCode: 'EUR' };
28+
const configuration: AmountServiceConfiguration = {
29+
showCurrencyCode: 'showForeignCurrency',
30+
currencyCodePosition: 'prefix',
31+
};
32+
spectator.service.transform(amount, configuration);
33+
expect(amountService.formatAmount).toHaveBeenCalledWith(amount, configuration);
34+
});
35+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
3+
import { Amount } from './amount.model';
4+
import { AmountService } from './amount.service';
5+
import { AmountServiceConfiguration } from './amount-service-formatter';
6+
7+
/**
8+
* Configuration object for the amount-pipe. The configuration object can be used to control
9+
* the formatting of the amount, and can be passed as an argument to the amount-pipe when used on an {@link Amount}.
10+
* - `showCurrencyCode`: Controls whether the currency code should be displayed or not.
11+
* - `''`: Don't output currency code
12+
* - `alwaysShowCurrency`: Always show currency code, regardless of presentation currency
13+
* - `showForeignCurrency`: Only show currency code if it differs from the presentation currency
14+
* - `digitsInfo`: A string that represents the format of the number. Learn more about the format here: https://angular.io/api/common/DecimalPipe#parameters
15+
* - `stripSign`: Controls whether the minus sign should be stripped from a negative amount.
16+
* - `currencyCodePosition`: Controls the position of the currency code in the formatted amount.
17+
* - `postfix`: Output currency code after the formatted amount, e.g. 1.234,56 EUR
18+
* - `prefix`: Output currency code before the formatted amount, e.g. DKK 1.234,56
19+
*/
20+
@Pipe({
21+
name: 'amount',
22+
standalone: true,
23+
})
24+
export class AmountPipe implements PipeTransform {
25+
constructor(private amountService: AmountService) {}
26+
27+
/**
28+
* Applies the transformation logic, by taking the `amount`-argument, and a configuration object - {@link AmountServiceConfiguration} (or a number of arguments, for backwards compatibility).
29+
*
30+
* @param amount the {@link Amount} to configure
31+
* @param amountServiceConfiguration
32+
*/
33+
transform(amount: Amount, amountServiceConfiguration: AmountServiceConfiguration) {
34+
return this.amountService.formatAmount(amount, amountServiceConfiguration);
35+
}
36+
}

0 commit comments

Comments
 (0)